|
| 1 | +# Database Schema |
| 2 | + |
| 3 | +FreemKit creates two tables on activation, both using the standard WordPress table prefix. The schema version is stored in the `freemkit_db_version` option and migrations run automatically on plugin update. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## `{prefix}freemkit_subscribers` |
| 8 | + |
| 9 | +Stores one row per unique subscriber email address. This is the primary record of every customer FreemKit has processed. |
| 10 | + |
| 11 | +```sql |
| 12 | +CREATE TABLE {prefix}freemkit_subscribers ( |
| 13 | + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, |
| 14 | + email VARCHAR(100) NOT NULL, |
| 15 | + first_name VARCHAR(50) DEFAULT '', |
| 16 | + last_name VARCHAR(50) DEFAULT '', |
| 17 | + status VARCHAR(20) NOT NULL DEFAULT 'active', |
| 18 | + marketing_optout TINYINT(1) NOT NULL DEFAULT 0, |
| 19 | + created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 20 | + modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| 21 | + PRIMARY KEY (id), |
| 22 | + UNIQUE KEY email (email), |
| 23 | + KEY status (status), |
| 24 | + KEY marketing_optout (marketing_optout) |
| 25 | +); |
| 26 | +``` |
| 27 | + |
| 28 | +### Column Descriptions |
| 29 | + |
| 30 | +| Column | Type | Description | |
| 31 | +|---|---|---| |
| 32 | +| `id` | `BIGINT UNSIGNED` | Auto-increment primary key | |
| 33 | +| `email` | `VARCHAR(100)` | Subscriber email address. Unique — one row per address | |
| 34 | +| `first_name` | `VARCHAR(50)` | First name as received from Freemius | |
| 35 | +| `last_name` | `VARCHAR(50)` | Last name as received from Freemius | |
| 36 | +| `status` | `VARCHAR(20)` | `active` or `opted_out` | |
| 37 | +| `marketing_optout` | `TINYINT(1)` | `1` if the user has opted out of marketing. FreemKit will not subscribe opted-out users when the Respect Marketing Opt-Out setting is enabled | |
| 38 | +| `created` | `DATETIME` | Timestamp of first record insertion | |
| 39 | +| `modified` | `DATETIME` | Timestamp of last update (auto-managed by MySQL) | |
| 40 | + |
| 41 | +### Notes |
| 42 | + |
| 43 | +- The `email` unique key means records are matched and upserted by email. A subscriber who triggers multiple events is stored as a single row — only their data is updated, not duplicated. |
| 44 | +- `status` and `marketing_optout` are separate fields because `opted_out` status is a user-visible list-management state, whereas `marketing_optout` reflects Freemius's marketing consent flag specifically. |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## `{prefix}freemkit_subscriber_events` |
| 49 | + |
| 50 | +Stores one row per processed webhook event per subscriber/plugin combination. This is the event history. |
| 51 | + |
| 52 | +```sql |
| 53 | +CREATE TABLE {prefix}freemkit_subscriber_events ( |
| 54 | + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, |
| 55 | + subscriber_id BIGINT(20) UNSIGNED NOT NULL, |
| 56 | + plugin_id VARCHAR(50) NOT NULL DEFAULT '', |
| 57 | + plugin_slug VARCHAR(100) NOT NULL DEFAULT '', |
| 58 | + event_type VARCHAR(100) NOT NULL DEFAULT '', |
| 59 | + user_type VARCHAR(20) NOT NULL DEFAULT '', |
| 60 | + form_ids TEXT DEFAULT NULL, |
| 61 | + tag_ids TEXT DEFAULT NULL, |
| 62 | + freemius_user_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, |
| 63 | + created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 64 | + PRIMARY KEY (id), |
| 65 | + KEY subscriber_id (subscriber_id), |
| 66 | + KEY plugin_id (plugin_id), |
| 67 | + KEY event_type (event_type), |
| 68 | + KEY user_type (user_type), |
| 69 | + KEY created (created) |
| 70 | +); |
| 71 | +``` |
| 72 | + |
| 73 | +### Column Descriptions |
| 74 | + |
| 75 | +| Column | Type | Description | |
| 76 | +|---|---|---| |
| 77 | +| `id` | `BIGINT UNSIGNED` | Auto-increment primary key | |
| 78 | +| `subscriber_id` | `BIGINT UNSIGNED` | Foreign key to `freemkit_subscribers.id` | |
| 79 | +| `plugin_id` | `VARCHAR(50)` | Freemius numeric product ID | |
| 80 | +| `plugin_slug` | `VARCHAR(100)` | Plugin name as a slug (for human-readable reference) | |
| 81 | +| `event_type` | `VARCHAR(100)` | Freemius event type (e.g. `license.created`) | |
| 82 | +| `user_type` | `VARCHAR(20)` | User classification: `free`, `paid`, `opted_out`, or empty string for opt-in and name-change events | |
| 83 | +| `form_ids` | `TEXT` | Comma-separated list of Kit form IDs the subscriber was added to | |
| 84 | +| `tag_ids` | `TEXT` | Comma-separated list of Kit tag IDs that were applied | |
| 85 | +| `freemius_user_id` | `BIGINT UNSIGNED` | Freemius user ID as provided in the webhook payload. `0` when not present. | |
| 86 | +| `created` | `DATETIME` | Timestamp when the event was processed | |
| 87 | + |
| 88 | +### Notes |
| 89 | + |
| 90 | +- Each webhook that results in a Kit action produces one row here. Multiple events from the same subscriber across different plugins each produce separate rows. |
| 91 | +- `form_ids` and `tag_ids` store the IDs that were used at time of processing. If you later change the form mapping in settings, older event rows are unaffected. |
| 92 | +- There is no foreign key constraint defined at the database level (WordPress convention), but `subscriber_id` always references a valid `freemkit_subscribers.id` row. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## WordPress Options |
| 97 | + |
| 98 | +FreemKit stores the following entries in `wp_options`: |
| 99 | + |
| 100 | +| Option Key | Autoloaded | Description | |
| 101 | +|---|---|---| |
| 102 | +| `freemkit_settings` | Yes | Serialised array containing all plugin settings | |
| 103 | +| `freemkit_audit_log` | No | Serialised audit log entries array (max 200) | |
| 104 | +| `freemkit_db_version` | Yes | Installed database schema version | |
| 105 | +| `freemkit_wizard_completed` | Yes | `1` when the setup wizard has been completed | |
| 106 | +| `freemkit_wizard_current_step` | Yes | Current wizard step number (integer) | |
| 107 | +| `freemkit_show_wizard` | Yes | Whether to show the wizard on next admin load | |
| 108 | + |
| 109 | +The `freemkit_settings` option contains all user-facing configuration. Sensitive values (`secret_key`, `kit_access_token`, `kit_refresh_token`) are encrypted at rest using AES-256-CBC (OpenSSL) or libsodium, depending on what is available on the server. |
| 110 | + |
| 111 | +Access settings via `Options_API::get_option( $key )` rather than reading the option directly. This applies the `freemkit_get_option` filter chain and handles decryption transparently. |
0 commit comments