Formidable Forms saves forms, fields, entries, and payments in its own database tables. This database schema reference lists the columns in each table and shows how the tables connect, so you can query your form data directly and find data to clean up.
Upgrade your WordPress site with powerful, flexible forms.
Table relationships
Each form has fields and entries. Each entry has a row for every saved field value. These are the columns that link the tables:
| Column | Points to | What it links |
|---|---|---|
| wp_frm_fields.form_id | wp_frm_forms.id | The form a field belongs to. |
| wp_frm_items.form_id | wp_frm_forms.id | The form an entry was submitted in. |
| wp_frm_item_metas.item_id | wp_frm_items.id | The entry a saved value belongs to. |
| wp_frm_item_metas.field_id | wp_frm_fields.id | The field a saved value belongs to. |
| wp_frm_items.user_id | wp_users.ID | The user who owns the entry. |
| wp_frm_items.post_id | wp_posts.ID | The post that a Create Post action made from the entry. |
| wp_frm_items.parent_item_id | wp_frm_items.id | The parent entry of a Repeater row or an embedded form entry. |
| wp_frm_forms.parent_form_id | wp_frm_forms.id | The form that a Repeater's own form sits in. |
| wp_frm_payments.item_id | wp_frm_items.id | The entry a payment was made for. |
| wp_frm_payments.action_id | wp_posts.ID | The payment form action (a frm_form_actions post). |
| wp_frm_payments.sub_id | wp_frm_subscriptions.id | The subscription a recurring payment belongs to. |
| wp_frm_subscriptions.item_id | wp_frm_items.id | The entry a subscription was made for. |
These are logical relationships. The tables don't define foreign keys, so the database doesn't enforce them. Deleting a row directly in the database doesn't delete the rows that point to it.
To delete an entry, use the Formidable → Entries page or FrmEntry::destroy(), which deletes the entry and its saved values. With Formidable Forms Pro, it also deletes the entry's Repeater rows and embedded form entries, and moves the post made from it to the trash. The entry's payment rows stay in wp_frm_payments.
From Idea to Reality in Minutes. Build Powerful Forms, Dashboards, Apps and More.
Formidable Forms makes advanced site building simple. Launch forms, directories, dashboards, and custom WordPress apps faster than ever before.
wp_frm_forms
This table has a row for each form.
| Column | Type | What it stores |
|---|---|---|
| id | INT(11) auto_increment | The form ID. |
| form_key | VARCHAR(100) | The unique form key. |
| name | VARCHAR(255) | The form title. |
| description | TEXT | The form description. |
| parent_form_id | INT(11) | For the form that holds a Repeater's fields, the ID of the form the Repeater is in. Otherwise 0. |
| logged_in | TINYINT(1) | 1 when Limit form visibility is on. |
| editable | TINYINT(1) | 1 when Allow front-end editing of entries is on. |
| is_template | TINYINT(1) | 1 for a form saved as a template. |
| default_template | TINYINT(1) | Used by the templates of older versions. |
| status | VARCHAR(255) | published, draft, or trash. |
| options | LONGTEXT | The form settings, serialized. |
| created_at | DATETIME | When the form was created, in UTC. |
wp_frm_fields
This table has a row for each field. Each field ID is unique across all forms.
| Column | Type | What it stores |
|---|---|---|
| id | BIGINT(20) auto_increment | The field ID. |
| field_key | VARCHAR(100) | The unique field key. |
| name | TEXT | The field label. |
| description | LONGTEXT | The field description. |
| type | TEXT | The field type, such as text, email, or checkbox. |
| default_value | LONGTEXT | The default value. |
| options | LONGTEXT | The choices of a field with options, such as Checkboxes or Dropdown, serialized. |
| field_order | INT(11) | The field's position in the form. |
| required | INT(1) | 1 when the field is required. |
| field_options | LONGTEXT | The field settings, serialized. For a Repeater or Embed Form field, form_select holds the ID of the form with its fields. |
| form_id | INT(11) | The ID of the form the field belongs to. |
| created_at | DATETIME | When the field was created, in UTC. |
wp_frm_items
This table has a row for each entry. Field values aren't in this table: they're in wp_frm_item_metas.
| Column | Type | What it stores |
|---|---|---|
| id | BIGINT(20) auto_increment | The entry ID. |
| item_key | VARCHAR(100) | The unique entry key. |
| name | VARCHAR(255) | The entry name. |
| description | TEXT | The submitter's browser and referring page. |
| ip | TEXT | The submitter's IP address. Empty when Do not store user IPs with form submissions is on. |
| form_id | BIGINT(20) | The ID of the form the entry was submitted in. |
| post_id | BIGINT(20) | The ID of the post a Create Post action made from the entry. Otherwise 0. |
| user_id | BIGINT(20) | The ID of the user who owns the entry. 0 for a logged-out visitor. |
| parent_item_id | BIGINT(20) | For a Repeater row or an embedded form entry, the ID of the parent entry. Otherwise 0. |
| is_draft | TINYINT(1) | The entry status: 0 submitted, 1 draft, 4 spam. The Form Abandonment add-on adds 2 in progress and 3 abandoned. |
| updated_by | BIGINT(20) | The ID of the user who last saved the entry. |
| created_at | DATETIME | When the entry was created, in UTC. |
| updated_at | DATETIME | When the entry was last updated, in UTC. |
wp_frm_item_metas
This table has a row for each field value saved in an entry.
| Column | Type | What it stores |
|---|---|---|
| id | BIGINT(20) auto_increment | The row ID. |
| meta_value | LONGTEXT | The saved value. A field with several values, such as Checkboxes, saves a serialized array. |
| field_id | BIGINT(20) | The ID of the field the value belongs to. |
| item_id | BIGINT(20) | The ID of the entry the value belongs to. |
| created_at | DATETIME | When the value was saved, in UTC. |
A row with field_id 0 holds entry details that don't belong to a field, such as the ID used to detect duplicate submissions. In Formidable Forms Pro, these rows also hold entry comments.
wp_frm_payments
This table has a row for each payment made through a payment form action, such as Stripe, Square, or PayPal.
| Column | Type | What it stores |
|---|---|---|
| id | BIGINT(20) NOT NULL auto_increment | The payment ID. |
| meta_value | LONGTEXT default NULL | Payment details, serialized. |
| receipt_id | VARCHAR(100) default NULL | The payment ID from the payment gateway. |
| invoice_id | VARCHAR(100) default NULL | The invoice ID from the payment gateway. |
| sub_id | VARCHAR(100) default NULL | For a recurring payment, the id of its row in wp_frm_subscriptions. |
| item_id | BIGINT(20) NOT NULL | The ID of the entry the payment was made for. |
| action_id | BIGINT(20) NOT NULL | The ID of the payment form action. |
| amount | DECIMAL(12,2) NOT NULL default '0.00' | The amount. |
| status | VARCHAR(100) default NULL | The payment status, such as complete or failed. |
| begin_date | DATE NOT NULL | The payment date. |
| expire_date | DATE default NULL | The date the payment period ends, for a recurring payment. |
| paysys | VARCHAR(100) default NULL | The payment gateway, such as stripe. |
| created_at | DATETIME NOT NULL | When the row was created, in UTC. |
| test | TINYINT(1) default NULL | 1 for a payment made in test mode. |
A site that had the PayPal Standard add-on active at any time also has a completed column (0 or 1), which that add-on created. Formidable Forms 7.0 doesn't create or use it, except to copy old values into status when it updates the table.
wp_frm_subscriptions
This table has a row for each recurring payment subscription.
| Column | Type | What it stores |
|---|---|---|
| id | BIGINT(20) NOT NULL auto_increment | The subscription ID. Payments for this subscription have this value in sub_id. |
| sub_id | VARCHAR(100) default NULL | The subscription ID from the payment gateway. |
| meta_value | LONGTEXT default NULL | Subscription details, serialized. |
| item_id | BIGINT(20) NOT NULL | The ID of the entry the subscription was made for. |
| action_id | BIGINT(20) NOT NULL | The ID of the payment form action. |
| amount | DECIMAL(12,2) NOT NULL default '0.00' | The amount of each payment. |
| first_amount | DECIMAL(12,2) NOT NULL default '0.00' | The first payment amount, when it differs from the recurring amount. Usually 0.00. |
| interval_count | BIGINT(20) default 1 | How many intervals pass between payments. |
| time_interval | VARCHAR(100) default NULL | The interval, such as month or year. |
| fail_count | BIGINT(20) default 0 | The number of failed payments. |
| end_count | BIGINT(20) default NULL | The number of payments after which the subscription ends, when set. |
| next_bill_date | DATE default NULL | The date of the next payment. |
| status | VARCHAR(100) default NULL | The subscription status, such as active or canceled. |
| paysys | VARCHAR(100) default NULL | The payment gateway, such as stripe. |
| created_at | DATETIME NOT NULL | When the row was created, in UTC. |
| test | TINYINT(1) default NULL | 1 for a subscription made in test mode. |
Other tables
Some features add tables of their own:
- wp_frm_gated_tokens: the access tokens of gated content. entry_id points to wp_frm_items.id and action_id to the form action.
- wp_frm_pro_meta_index: an index of short field values that Formidable Forms Pro uses to speed up Lookup fields and Views. Formidable builds it from wp_frm_item_metas, which stays the source of the values, so don't edit it.
- wp_frm_view_layouts: the listing and detail layouts built in the Views layout builder, added by the Formidable Views add-on. view_id points to the View's post ID.
- wp_frmpro_copies: on a multisite network, the forms and Views set to be copied to the other sites.
Repeater and embedded form entries
A Repeater field's fields are in a form of their own, whose parent_form_id is the ID of the main form. An Embed Form field shows another form, which keeps parent_form_id 0.
When an entry is saved, each Repeater row and each embedded form entry is saved as its own entry in wp_frm_items. Its parent_item_id is the main entry's ID. In the main entry, the Repeater or Embed Form field's value in wp_frm_item_metas is a serialized list of those entry IDs.
Find a form's entries
This query lists the submitted entries of a form, with each saved value and its field label. Replace 5 with your form ID. To find it, go to Formidable → Forms and check the ID column.
SELECT e.id AS entry_id, e.created_at, f.name AS field_name, m.meta_value
FROM wp_frm_items e
INNER JOIN wp_frm_item_metas m ON m.item_id = e.id
INNER JOIN wp_frm_fields f ON f.id = m.field_id
WHERE e.form_id = 5
AND e.is_draft = 0
ORDER BY e.id, f.field_order;To use the form key instead of the form ID, join wp_frm_forms. Replace contact-form with your form key.
SELECT e.id AS entry_id, e.created_at
FROM wp_frm_items e
INNER JOIN wp_frm_forms fo ON fo.id = e.form_id
WHERE fo.form_key = 'contact-form'
AND e.is_draft = 0
ORDER BY e.created_at DESC;To list the Repeater rows of an entry with their values, look up the entries whose parent_item_id is that entry's ID. Replace 25 with the entry ID.
Embedded form entries use parent_item_id too. To skip them, the query joins wp_frm_forms and keeps rows from a form with a parent_form_id, which only a Repeater's form has.
SELECT r.id AS row_entry_id, f.name AS field_name, m.meta_value
FROM wp_frm_items r
INNER JOIN wp_frm_item_metas m ON m.item_id = r.id
INNER JOIN wp_frm_fields f ON f.id = m.field_id
INNER JOIN wp_frm_forms fo ON fo.id = r.form_id
WHERE r.parent_item_id = 25
AND fo.parent_form_id <> 0
ORDER BY r.id, f.field_order;To list the payments for a form's entries, join wp_frm_payments on item_id. Replace 5 with your form ID.
SELECT e.id AS entry_id, p.amount, p.status, p.paysys, p.begin_date
FROM wp_frm_payments p
INNER JOIN wp_frm_items e ON e.id = p.item_id
WHERE e.form_id = 5
ORDER BY p.begin_date DESC;In PHP, the same query runs through $wpdb, with the table prefix and the form ID filled in for you:
global $wpdb;
$form_id = 5; // Replace 5 with your form ID.
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT e.id AS entry_id, f.name AS field_name, m.meta_value
FROM {$wpdb->prefix}frm_items e
INNER JOIN {$wpdb->prefix}frm_item_metas m ON m.item_id = e.id
INNER JOIN {$wpdb->prefix}frm_fields f ON f.id = m.field_id
WHERE e.form_id = %d AND e.is_draft = 0
ORDER BY e.id, f.field_order",
$form_id
)
);A value from a field with several values comes back serialized. To get it as an array, use maybe_unserialize() on meta_value, or get the values with the PHP functions in get field values as an array.
Clean up the database
When the Formidable tables get large, delete the data you no longer need with the built-in tools. Before you delete anything, back up your database and your site files, and make sure the backup restores. A form or post in the trash can be restored from there, but data that's deleted for good comes back only from a backup.
Delete data with the built-in tools
- Delete the entries you no longer need from the form's Entries page. Learn how to bulk delete entries. Bulk deleting entries is a premium feature.
- Delete entries after a set number of days with Automatically delete entries in the form's GDPR settings. Deleting entries automatically is a premium feature.
- Delete old spam with Delete spam entries automatically in the Captcha/Spam global settings. Deleting spam automatically is a premium feature.
Check what else goes with an entry before you delete it. Deleting an entry from its row, or deleting selected entries in bulk, also deletes:
- Its saved values, its Repeater rows, and its embedded form entries.
- The post linked to it, such as one a Create Post action made. Posts and pages go to the trash. They're deleted for good when they're already in the trash, when your site turns off the WordPress trash, or when they're another post type.
- Its uploaded files, when the File Upload field is set to delete files with the entry. A file is deleted from the Media Library even if you use it somewhere else on your site.
It also runs the form actions set to run when an entry is deleted, and any custom code or other plugin that runs when an entry is deleted.
The Delete All Entries button works differently. It deletes the linked posts and files and runs those actions only when the form has a Create Post action, or when you check Trigger all actions that happen "on entry deleted". Learn more about deleting all entries.
Deleting a field deletes the values saved in that field, in every entry. Deleting a Repeater field also deletes all of its Repeater rows.
Permanently deleting a form deletes its fields, entries, saved values, and form actions. Its entries go the same way as entries deleted from their row, with their linked posts and files. It also deletes the Views and landing pages made for it. A form you delete goes to the trash first and, by default, is deleted for good after 30 days. If your site turns off the WordPress trash, the form is deleted at once. To delete trashed forms sooner, go to Formidable → Forms, open the Trash tab, and click Empty Trash. This deletes every form in the trash.
The Logs add-on saves its logs in wp_posts and wp_postmeta, not in the Formidable tables. To delete old logs, go to Formidable → Global Settings → Logs and turn on Automatically delete log entries every 30 days. When the cleanup runs, about once a month, it deletes the logs older than one week by default.
Find leftover data
Run the queries in phpMyAdmin, in your host's database tool, or with wp db query. Replace every wp_ in a query with your table prefix, including the ones inside quotes. The prefix is the $table_prefix value in wp-config.php. On a multisite network, use the prefix of the site you're checking. The main site uses the plain prefix, and the other sites add their site ID, such as wp_3_.
To see which Formidable tables are the largest, with their size in megabytes:
SELECT table_name, table_rows,
ROUND((data_length + index_length) / 1024 / 1024, 1) AS size_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name IN ('wp_frm_forms', 'wp_frm_fields', 'wp_frm_items', 'wp_frm_item_metas', 'wp_frm_payments', 'wp_frm_subscriptions', 'wp_frm_gated_tokens', 'wp_frm_pro_meta_index', 'wp_frm_view_layouts')
ORDER BY data_length + index_length DESC;On InnoDB tables, table_rows and the size are estimates.
Formidable deletes an entry's values and Repeater rows with the entry, and a field's values with the field. Rows deleted directly in the database, such as in phpMyAdmin, can leave these behind. These queries count them.
Entries whose form or parent entry no longer exists, such as the Repeater rows of an entry deleted directly:
SELECT COUNT(*)
FROM wp_frm_items e
LEFT JOIN wp_frm_forms fo ON fo.id = e.form_id
LEFT JOIN wp_frm_items p ON p.id = e.parent_item_id
WHERE fo.id IS NULL
OR (e.parent_item_id <> 0 AND p.id IS NULL);Values whose entry no longer exists:
SELECT COUNT(*)
FROM wp_frm_item_metas m
LEFT JOIN wp_frm_items e ON e.id = m.item_id
WHERE e.id IS NULL;Values whose field no longer exists. These values can belong to entries that still exist. The query skips rows with field_id 0, because they hold entry details, not field values.
SELECT COUNT(*)
FROM wp_frm_item_metas m
LEFT JOIN wp_frm_fields f ON f.id = m.field_id
WHERE m.field_id <> 0
AND f.id IS NULL;A count only shows that a link between rows is missing. It doesn't prove the rows aren't needed. The counts can also include rows from an import that didn't finish. Have a developer check that import on a staging copy before anything is removed.
Find the tables of deleted network sites
On a multisite network, each site other than the main site has its own Formidable tables, with the site ID in the table prefix, such as wp_3_frm_items. When you delete a site from the network, some of its Formidable tables can stay in the database. When Formidable isn't loaded while the site is deleted, such as when it's active only on that site, none of them are removed.
This query lists the Formidable tables whose site ID is no longer in wp_blogs and whose site's options table is gone too. Sites that are archived, marked as spam, or flagged for deletion are still in wp_blogs, so their tables aren't listed. Use your network's main table prefix in place of wp_.
SELECT t.table_name
FROM information_schema.tables t
LEFT JOIN wp_blogs b
ON CAST(b.blog_id AS CHAR) = SUBSTRING_INDEX(SUBSTRING(t.table_name, LENGTH('wp_') + 1), '_', 1)
WHERE t.table_schema = DATABASE()
AND t.table_name REGEXP '^wp_[0-9]+_frm_(fields|forms|items|item_metas|payments|subscriptions|gated_tokens|pro_meta_index|view_layouts)$'
AND b.blog_id IS NULL
AND NOT EXISTS (
SELECT 1 FROM information_schema.tables o
WHERE o.table_schema = DATABASE()
AND o.table_name = CONCAT('wp_', SUBSTRING_INDEX(SUBSTRING(t.table_name, LENGTH('wp_') + 1), '_', 1), '_options')
)
ORDER BY t.table_name;A listed table may belong to a deleted site. Before anyone removes it, make sure it belongs to this network and not to another site that shares the database. Also make sure the site was deleted on purpose and that you don't need its data. The payments and subscriptions tables can hold records you need to keep.
Remove leftover data
To remove the rows or tables these queries find, or to make a large table smaller on disk, ask your developer or your host. Before they remove anything, they should:
- Check each row or table and what depends on it. Repair or recover data that's still needed instead of removing it.
- Try the cleanup on a staging copy of the site first.
- Stop anything that writes to the database, such as form submissions, imports, and scheduled tasks. Then make a full backup of the database and the site files, and test that it restores.
- Run the queries again right before the cleanup, and keep a copy of the exact rows or tables they remove.