Database Schema

LicenceForge creates eight custom tables in your WordPress database, all prefixed with wplf_ (after the standard WordPress table prefix). This page documents every column, index, and foreign-key relationship.

Note

Tables are created and updated automatically during plugin activation and upgrades. Never modify the schema manually -- use the hooks and APIs described in the Extending guide instead.

Entity relationships

The diagram below describes how the eight tables relate to one another. Primary keys are marked PK and foreign keys are marked FK.

wplf_products (PK: id)
  |
  |--< wplf_product_prices (FK: product_id -> products.id, ON DELETE CASCADE)
  |
  |--< wplf_licenses (FK: product_id -> products.id, ON DELETE RESTRICT)
  |       |
  |       |--< wplf_activations (FK: license_id -> licenses.id, ON DELETE CASCADE)
  |       |
  |       |--< wplf_audit_log (FK: license_id -> licenses.id, ON DELETE SET NULL)
  |       |
  |       |--< wplf_analytics (license_id -- no FK constraint)
  |
  |--< wplf_api_keys (product_id -- no FK constraint, nullable)

wplf_licenses
  |--< FK: price_id -> wplf_product_prices.id (ON DELETE SET NULL)

wplf_webhook_events (standalone, no foreign keys)

Tip

Deleting a product is intentionally blocked (ON DELETE RESTRICT on wplf_licenses) to prevent accidental loss of licence records. Deactivate the product and revoke its licences first.

wplf_products

Stores every product (plugin or theme) managed by LicenceForge. Each product has a unique slug used throughout the REST API and client library.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
slugvarchar(100)UNIQUE, NOT NULLURL-safe identifier used in API calls.
namevarchar(255)NOT NULLHuman-readable product name.
descriptiontextOptional product description.
homepage_urlvarchar(255)Public product page URL.
latest_versionvarchar(20)DEFAULT '1.0.0'Current release version (semver).
zip_pathvarchar(500)Server path to the distributable ZIP file.
zip_hashvarchar(64)SHA-256 hash of the ZIP for integrity checks.
external_urlvarchar(500)Optional external download URL (overrides local ZIP).
requires_wpvarchar(10)DEFAULT '5.8'Minimum WordPress version required.
tested_wpvarchar(10)DEFAULT '6.4'Highest WordPress version tested.
requires_phpvarchar(10)DEFAULT '7.4'Minimum PHP version required.
changeloglongtextHTML changelog shown during updates.
is_activetinyint(1)DEFAULT 1Whether the product accepts new activations.
trial_enabledtinyint(1)DEFAULT 0Whether free trials are enabled.
trial_daysintDEFAULT 14Number of days for a trial licence.
rollout_percentageintDEFAULT 100Percentage of sites that receive the latest version.
rollout_versionvarchar(20)Version being rolled out (when < 100%).
rollout_pulledtinyint(1)DEFAULT 0Whether the rollout has been pulled (emergency stop).
require_fingerprinttinyint(1)DEFAULT 0Require server fingerprint for activation.
fingerprint_mismatch_actionvarchar(10)DEFAULT 'warn'Action on mismatch: warn or block.
created_atdatetimeRow creation timestamp.
updated_atdatetimeLast modification timestamp.

wplf_product_prices

Defines the pricing tiers available for each product. Each tier maps to a Stripe Price or WooCommerce Product and controls activation limits and feature gating.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
product_idbigint unsignedFK → products.id, ON DELETE CASCADEParent product.
labelvarchar(100)Tier label (e.g. "Personal", "Business").
stripe_price_idvarchar(100)Stripe Price ID (e.g. price_1Abc...).
wc_product_idbigint unsignedWooCommerce Product/Variation ID.
price_intervalenum('month','year','lifetime')NOT NULLBilling interval.
price_amountdecimal(10,2)NOT NULLPrice in the store currency.
activation_limitintNOT NULLMax concurrent site activations for this tier.
featurestextJSON array of feature slugs included in this tier.
is_activetinyint(1)DEFAULT 1Whether this tier is available for new purchases.
created_atdatetimeRow creation timestamp.

wplf_licenses

The core licence table. Each row represents a single licence key issued to a customer, linked to a product and optionally to a pricing tier, user, and payment source.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
product_idbigint unsignedFK → products.id, ON DELETE RESTRICTProduct this licence belongs to.
price_idbigint unsignedFK → product_prices.id, ON DELETE SET NULLPricing tier (nullable if tier deleted).
user_idbigint unsignedWordPress user ID (if linked).
order_idbigint unsignedWooCommerce order ID (if applicable).
stripe_subscription_idvarchar(100)Stripe Subscription ID (e.g. sub_1Abc...).
wc_subscription_idbigint unsignedWooCommerce Subscription ID.
license_key_hashvarchar(64)UNIQUE, NOT NULLSHA-256 hash of the licence key. The plaintext key is never stored.
statusvarchar(20)NOT NULL, DEFAULT 'active'Current status: active, expired, suspended, revoked, trial.
activation_limitintNOT NULL, DEFAULT 1Max concurrent activations.
current_period_enddatetimeSubscription period end (null for lifetime).
customer_emailvarchar(255)Customer email address.
customer_namevarchar(255)Customer display name.
created_atdatetimeRow creation timestamp.
updated_atdatetimeLast modification timestamp.

Indexes

Index name Column(s) Purpose
idx_licenses_createdcreated_atEfficient sorting and filtering by creation date.
idx_licenses_emailcustomer_emailFast lookup by customer email for admin search and GDPR exports.

wplf_activations

Tracks every site that has activated a licence. A licence can have multiple activation rows up to its activation_limit. Deactivated sites retain their row with a non-null deactivated_at timestamp.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
license_idbigint unsignedFK → licenses.id, ON DELETE CASCADEParent licence.
site_originvarchar(255)NOT NULLSite URL origin (scheme + host).
user_agenttextUser-Agent string at activation time.
server_fingerprintvarchar(64)SHA-256 server fingerprint (when enabled).
activated_atdatetimeNOT NULLWhen the activation occurred.
last_seen_atdatetimeLast successful validation heartbeat.
deactivated_atdatetimeWhen the activation was removed (null if active).

Indexes

Index name Column(s) Purpose
idx_activations_last_seenlast_seen_atIdentify stale activations for cleanup cron jobs.

wplf_webhook_events

Stores every inbound webhook event from Stripe (and potentially other sources). Used for idempotent processing, retry logic, and debugging failed deliveries.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
event_idvarchar(255)UNIQUE, NOT NULLExternal event identifier (e.g. Stripe evt_...).
event_typevarchar(100)NOT NULLEvent type (e.g. checkout.session.completed).
sourcevarchar(20)NOT NULLOrigin system: stripe, woocommerce, etc.
processed_atdatetimeWhen processing completed (null if pending).
processing_errortextError message if processing failed.
retry_countsmallintDEFAULT 0Number of processing attempts.
next_retry_atdatetimeScheduled next retry (null if not retrying).
payloadlongtextFull JSON event payload for replay/debugging.
created_atdatetimeNOT NULLWhen the event was received.

Indexes

Index name Column(s) Purpose
idx_webhook_sourcesourceFilter events by origin system.
idx_webhook_retrynext_retry_atEfficiently find events due for retry.

wplf_audit_log

Immutable audit trail of every significant action: validation attempts, activations, deactivations, key rotations, and admin operations. Used for security analysis and compliance.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
actionvarchar(100)NOT NULLAction identifier (e.g. license.validate, license.activate).
product_slugvarchar(100)Product slug involved (if applicable).
license_idbigint unsignedFK → licenses.id, ON DELETE SET NULLRelated licence (null if licence deleted).
site_origin_hintvarchar(255)Site URL that triggered the action.
outcomevarchar(20)Result: success, denied, error, rate_limited.
ip_hashvarchar(16)Truncated hash of the requester IP (privacy-safe).
metadatatextJSON blob with additional context.
created_atdatetimeNOT NULLWhen the event occurred.

Indexes

Index name Column(s) Purpose
idx_audit_outcomeoutcomeFilter by result type for security monitoring dashboards.

wplf_analytics

General-purpose event tracking for licence and site activity. Powers the analytics dashboard with activation trends, version distribution, and usage patterns.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
license_idbigint unsignedRelated licence (no FK constraint for performance).
site_originvarchar(255)Site URL that generated the event.
event_typevarchar(50)NOT NULLEvent category (e.g. activation, update_check, validation).
event_datatextJSON payload with event-specific details.
recorded_atdatetimeNOT NULLWhen the event was recorded.

wplf_api_keys

Stores hashed API keys used for authenticating external integrations against the admin REST endpoints. Keys are scoped by permission level and optionally restricted to a single product.

Column Type Attributes Description
idbigint unsignedPK, AUTO_INCREMENTPrimary key.
api_key_hashvarchar(64)UNIQUE, NOT NULLSHA-256 hash of the API key. The plaintext key is shown once at creation.
api_key_prefixvarchar(8)NOT NULLFirst 8 characters of the key for identification in the admin UI.
labelvarchar(255)NOT NULLHuman-readable label (e.g. "CI/CD Pipeline", "CRM Integration").
product_idbigint unsignedRestrict this key to a single product (null = all products).
permissionsvarchar(20)NOT NULL, DEFAULT 'read'Permission level: read, write, or admin.
last_used_atdatetimeTimestamp of the most recent API call with this key.
is_activetinyint(1)DEFAULT 1Whether the key is active (set to 0 to revoke).
created_atdatetimeNOT NULLWhen the key was created.

SQL creation reference

The following is a simplified representation of the CREATE TABLE statements. The actual statements use $wpdb->prefix and are generated by the installer class.

CREATE TABLE wplf_products (
    id              bigint unsigned NOT NULL AUTO_INCREMENT,
    slug            varchar(100)    NOT NULL,
    name            varchar(255)    NOT NULL,
    description     text,
    homepage_url    varchar(255),
    latest_version  varchar(20)     DEFAULT '1.0.0',
    zip_path        varchar(500),
    zip_hash        varchar(64),
    external_url    varchar(500),
    requires_wp     varchar(10)     DEFAULT '5.8',
    tested_wp       varchar(10)     DEFAULT '6.4',
    requires_php    varchar(10)     DEFAULT '7.4',
    changelog       longtext,
    is_active       tinyint(1)      DEFAULT 1,
    trial_enabled   tinyint(1)      DEFAULT 0,
    trial_days      int             DEFAULT 14,
    rollout_percentage int          DEFAULT 100,
    rollout_version varchar(20),
    rollout_pulled  tinyint(1)      DEFAULT 0,
    require_fingerprint tinyint(1)  DEFAULT 0,
    fingerprint_mismatch_action varchar(10) DEFAULT 'warn',
    created_at      datetime,
    updated_at      datetime,
    PRIMARY KEY (id),
    UNIQUE KEY slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_product_prices (
    id              bigint unsigned NOT NULL AUTO_INCREMENT,
    product_id      bigint unsigned NOT NULL,
    label           varchar(100),
    stripe_price_id varchar(100),
    wc_product_id   bigint unsigned,
    price_interval  enum('month','year','lifetime') NOT NULL,
    price_amount    decimal(10,2)   NOT NULL,
    activation_limit int            NOT NULL,
    features        text,
    is_active       tinyint(1)      DEFAULT 1,
    created_at      datetime,
    PRIMARY KEY (id),
    FOREIGN KEY (product_id) REFERENCES wplf_products(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_licenses (
    id                      bigint unsigned NOT NULL AUTO_INCREMENT,
    product_id              bigint unsigned NOT NULL,
    price_id                bigint unsigned,
    user_id                 bigint unsigned,
    order_id                bigint unsigned,
    stripe_subscription_id  varchar(100),
    wc_subscription_id      bigint unsigned,
    license_key_hash        varchar(64)     NOT NULL,
    status                  varchar(20)     NOT NULL DEFAULT 'active',
    activation_limit        int             NOT NULL DEFAULT 1,
    current_period_end      datetime,
    customer_email          varchar(255),
    customer_name           varchar(255),
    created_at              datetime,
    updated_at              datetime,
    PRIMARY KEY (id),
    UNIQUE KEY license_key_hash (license_key_hash),
    KEY idx_licenses_created (created_at),
    KEY idx_licenses_email (customer_email),
    FOREIGN KEY (product_id) REFERENCES wplf_products(id)        ON DELETE RESTRICT,
    FOREIGN KEY (price_id)   REFERENCES wplf_product_prices(id)  ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_activations (
    id                 bigint unsigned NOT NULL AUTO_INCREMENT,
    license_id         bigint unsigned NOT NULL,
    site_origin        varchar(255)    NOT NULL,
    user_agent         text,
    server_fingerprint varchar(64),
    activated_at       datetime        NOT NULL,
    last_seen_at       datetime,
    deactivated_at     datetime,
    PRIMARY KEY (id),
    KEY idx_activations_last_seen (last_seen_at),
    FOREIGN KEY (license_id) REFERENCES wplf_licenses(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_webhook_events (
    id               bigint unsigned NOT NULL AUTO_INCREMENT,
    event_id         varchar(255)    NOT NULL,
    event_type       varchar(100)    NOT NULL,
    source           varchar(20)     NOT NULL,
    processed_at     datetime,
    processing_error text,
    retry_count      smallint        DEFAULT 0,
    next_retry_at    datetime,
    payload          longtext,
    created_at       datetime        NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY event_id (event_id),
    KEY idx_webhook_source (source),
    KEY idx_webhook_retry (next_retry_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_audit_log (
    id               bigint unsigned NOT NULL AUTO_INCREMENT,
    action           varchar(100)    NOT NULL,
    product_slug     varchar(100),
    license_id       bigint unsigned,
    site_origin_hint varchar(255),
    outcome          varchar(20),
    ip_hash          varchar(16),
    metadata         text,
    created_at       datetime        NOT NULL,
    PRIMARY KEY (id),
    KEY idx_audit_outcome (outcome),
    FOREIGN KEY (license_id) REFERENCES wplf_licenses(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_analytics (
    id           bigint unsigned NOT NULL AUTO_INCREMENT,
    license_id   bigint unsigned,
    site_origin  varchar(255),
    event_type   varchar(50)     NOT NULL,
    event_data   text,
    recorded_at  datetime        NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE wplf_api_keys (
    id             bigint unsigned NOT NULL AUTO_INCREMENT,
    api_key_hash   varchar(64)     NOT NULL,
    api_key_prefix varchar(8)      NOT NULL,
    label          varchar(255)    NOT NULL,
    product_id     bigint unsigned,
    permissions    varchar(20)     NOT NULL DEFAULT 'read',
    last_used_at   datetime,
    is_active      tinyint(1)      DEFAULT 1,
    created_at     datetime        NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY api_key_hash (api_key_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;