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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
slug | varchar(100) | UNIQUE, NOT NULL | URL-safe identifier used in API calls. |
name | varchar(255) | NOT NULL | Human-readable product name. |
description | text | Optional product description. | |
homepage_url | varchar(255) | Public product page URL. | |
latest_version | varchar(20) | DEFAULT '1.0.0' | Current release version (semver). |
zip_path | varchar(500) | Server path to the distributable ZIP file. | |
zip_hash | varchar(64) | SHA-256 hash of the ZIP for integrity checks. | |
external_url | varchar(500) | Optional external download URL (overrides local ZIP). | |
requires_wp | varchar(10) | DEFAULT '5.8' | Minimum WordPress version required. |
tested_wp | varchar(10) | DEFAULT '6.4' | Highest WordPress version tested. |
requires_php | varchar(10) | DEFAULT '7.4' | Minimum PHP version required. |
changelog | longtext | HTML changelog shown during updates. | |
is_active | tinyint(1) | DEFAULT 1 | Whether the product accepts new activations. |
trial_enabled | tinyint(1) | DEFAULT 0 | Whether free trials are enabled. |
trial_days | int | DEFAULT 14 | Number of days for a trial licence. |
rollout_percentage | int | DEFAULT 100 | Percentage of sites that receive the latest version. |
rollout_version | varchar(20) | Version being rolled out (when < 100%). | |
rollout_pulled | tinyint(1) | DEFAULT 0 | Whether the rollout has been pulled (emergency stop). |
require_fingerprint | tinyint(1) | DEFAULT 0 | Require server fingerprint for activation. |
fingerprint_mismatch_action | varchar(10) | DEFAULT 'warn' | Action on mismatch: warn or block. |
created_at | datetime | Row creation timestamp. | |
updated_at | datetime | Last 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
product_id | bigint unsigned | FK → products.id, ON DELETE CASCADE | Parent product. |
label | varchar(100) | Tier label (e.g. "Personal", "Business"). | |
stripe_price_id | varchar(100) | Stripe Price ID (e.g. price_1Abc...). | |
wc_product_id | bigint unsigned | WooCommerce Product/Variation ID. | |
price_interval | enum('month','year','lifetime') | NOT NULL | Billing interval. |
price_amount | decimal(10,2) | NOT NULL | Price in the store currency. |
activation_limit | int | NOT NULL | Max concurrent site activations for this tier. |
features | text | JSON array of feature slugs included in this tier. | |
is_active | tinyint(1) | DEFAULT 1 | Whether this tier is available for new purchases. |
created_at | datetime | Row 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
product_id | bigint unsigned | FK → products.id, ON DELETE RESTRICT | Product this licence belongs to. |
price_id | bigint unsigned | FK → product_prices.id, ON DELETE SET NULL | Pricing tier (nullable if tier deleted). |
user_id | bigint unsigned | WordPress user ID (if linked). | |
order_id | bigint unsigned | WooCommerce order ID (if applicable). | |
stripe_subscription_id | varchar(100) | Stripe Subscription ID (e.g. sub_1Abc...). | |
wc_subscription_id | bigint unsigned | WooCommerce Subscription ID. | |
license_key_hash | varchar(64) | UNIQUE, NOT NULL | SHA-256 hash of the licence key. The plaintext key is never stored. |
status | varchar(20) | NOT NULL, DEFAULT 'active' | Current status: active, expired, suspended, revoked, trial. |
activation_limit | int | NOT NULL, DEFAULT 1 | Max concurrent activations. |
current_period_end | datetime | Subscription period end (null for lifetime). | |
customer_email | varchar(255) | Customer email address. | |
customer_name | varchar(255) | Customer display name. | |
created_at | datetime | Row creation timestamp. | |
updated_at | datetime | Last modification timestamp. |
Indexes
| Index name | Column(s) | Purpose |
|---|---|---|
idx_licenses_created | created_at | Efficient sorting and filtering by creation date. |
idx_licenses_email | customer_email | Fast 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
license_id | bigint unsigned | FK → licenses.id, ON DELETE CASCADE | Parent licence. |
site_origin | varchar(255) | NOT NULL | Site URL origin (scheme + host). |
user_agent | text | User-Agent string at activation time. | |
server_fingerprint | varchar(64) | SHA-256 server fingerprint (when enabled). | |
activated_at | datetime | NOT NULL | When the activation occurred. |
last_seen_at | datetime | Last successful validation heartbeat. | |
deactivated_at | datetime | When the activation was removed (null if active). |
Indexes
| Index name | Column(s) | Purpose |
|---|---|---|
idx_activations_last_seen | last_seen_at | Identify 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
event_id | varchar(255) | UNIQUE, NOT NULL | External event identifier (e.g. Stripe evt_...). |
event_type | varchar(100) | NOT NULL | Event type (e.g. checkout.session.completed). |
source | varchar(20) | NOT NULL | Origin system: stripe, woocommerce, etc. |
processed_at | datetime | When processing completed (null if pending). | |
processing_error | text | Error message if processing failed. | |
retry_count | smallint | DEFAULT 0 | Number of processing attempts. |
next_retry_at | datetime | Scheduled next retry (null if not retrying). | |
payload | longtext | Full JSON event payload for replay/debugging. | |
created_at | datetime | NOT NULL | When the event was received. |
Indexes
| Index name | Column(s) | Purpose |
|---|---|---|
idx_webhook_source | source | Filter events by origin system. |
idx_webhook_retry | next_retry_at | Efficiently 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
action | varchar(100) | NOT NULL | Action identifier (e.g. license.validate, license.activate). |
product_slug | varchar(100) | Product slug involved (if applicable). | |
license_id | bigint unsigned | FK → licenses.id, ON DELETE SET NULL | Related licence (null if licence deleted). |
site_origin_hint | varchar(255) | Site URL that triggered the action. | |
outcome | varchar(20) | Result: success, denied, error, rate_limited. | |
ip_hash | varchar(16) | Truncated hash of the requester IP (privacy-safe). | |
metadata | text | JSON blob with additional context. | |
created_at | datetime | NOT NULL | When the event occurred. |
Indexes
| Index name | Column(s) | Purpose |
|---|---|---|
idx_audit_outcome | outcome | Filter 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
license_id | bigint unsigned | Related licence (no FK constraint for performance). | |
site_origin | varchar(255) | Site URL that generated the event. | |
event_type | varchar(50) | NOT NULL | Event category (e.g. activation, update_check, validation). |
event_data | text | JSON payload with event-specific details. | |
recorded_at | datetime | NOT NULL | When 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 |
|---|---|---|---|
id | bigint unsigned | PK, AUTO_INCREMENT | Primary key. |
api_key_hash | varchar(64) | UNIQUE, NOT NULL | SHA-256 hash of the API key. The plaintext key is shown once at creation. |
api_key_prefix | varchar(8) | NOT NULL | First 8 characters of the key for identification in the admin UI. |
label | varchar(255) | NOT NULL | Human-readable label (e.g. "CI/CD Pipeline", "CRM Integration"). |
product_id | bigint unsigned | Restrict this key to a single product (null = all products). | |
permissions | varchar(20) | NOT NULL, DEFAULT 'read' | Permission level: read, write, or admin. |
last_used_at | datetime | Timestamp of the most recent API call with this key. | |
is_active | tinyint(1) | DEFAULT 1 | Whether the key is active (set to 0 to revoke). |
created_at | datetime | NOT NULL | When 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;
Related pages
- Hooks Reference -- Hook data often maps directly to these table columns
- REST API Reference -- API responses reflect this schema
- Extending LicenceForge -- Build custom integrations on top of the schema
- Encryption & Security -- How licence keys and API keys are hashed