Forms are stored in the wp_formforge_forms table. The fields, settings, and conditional_logic columns contain JSON-encoded strings that are automatically decoded when read through the PHP API.
Database Schema
CREATE TABLE wp_formforge_forms (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL DEFAULT '',
fields longtext DEFAULT '',
settings text DEFAULT '',
conditional_logic text DEFAULT '',
status varchar(20) NOT NULL DEFAULT 'publish',
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Field Properties Reference
| Property | Type | Applies To | Description |
|---|---|---|---|
id | string | All | Unique field identifier (e.g., field_1) |
type | string | All | Field type slug |
label | string | All | Display label |
required | boolean | All | Whether the field must be filled |
placeholder | string | text, email, phone, number, url, textarea, select | Placeholder text |
description | string | All | Help text shown below the field |
min_length | int | text, email, textarea, password, url | Minimum character length (browser HTML5 + server) |
max_length | int | text, email, textarea, password, url | Maximum character length (browser HTML5 + server) |
regex | string | text, password, url | Custom regex validation pattern (no PHP delimiters; renderer wraps in ~...~u) |
regex_message | string | text, password, url | Error message when regex fails (rendered as the title attribute and as the server error response) |
default_value | string | text, email, number, url, password | Pre-fills the field on first render |
css_class | string | All | Extra class on the field wrapper (in addition to formforge-field formforge-field--{type}) |
options | array | select, radio, checkbox | Array of {value, label} objects |
rows | int | textarea | Number of visible text rows |
min | number | number, range | Minimum numeric value (server: value < min rejected; range renders this as the slider min) |
max | number | number, range | Maximum numeric value (server: value > max rejected; range renders this as the slider max) |
step | number | number, range | Step increment. Server enforces (value - min) % step == 0 (float-safe via fmod + 0.0001 epsilon). Range uses this for the slider step. Skipped when step <= 0 or unset. |
max_stars | int | rating | Maximum star count (1-10) |
accept | string | file_upload | Comma-separated MIME types (image/jpeg,application/pdf) and/or extensions (.csv). Used as both the HTML5 accept attribute and the server-side allowlist; magic-byte type via wp_check_filetype_and_ext() must match. |
max_size_mb | int | file_upload | Per-field upload size cap (default 10, hard-capped at 100). |
amount | int | payment | Legacy — amount in smallest currency unit (cents). Still honoured for backward-compat when amount_type is unset. New fields use amount_type + amount_value instead. |
amount_type | string | payment | "fixed" (default) or "dynamic". fixed reads amount_value; dynamic reads the live value of the field referenced by amount_field_id at submit time. |
amount_value | number | payment | When amount_type === "fixed": the price in major currency units (e.g. 49.99, not 4999). Renderer multiplies by 100 for the Stripe amount cents value. Stripe minimum is 0.50. |
amount_field_id | string | payment | When amount_type === "dynamic": the id of the in-form Calculation field whose result becomes the charge. Renderer emits data-amount-source="formforge_field_" on the wrapper; stripe-field.js subscribes to input/change plus formforge:calculation-updated on the linked input and refreshes the displayed amount. On submit the browser blocks values below Stripe’s minimum before PaymentIntent creation, and class-stripe.php recomputes the expected cents from the posted Calculation input before calling Stripe. |
currency | string | payment | Three-letter currency code, lowercase. Allowlist: usd, eur, gbp, cad, aud, jpy, rub, cny, inr, brl, mxn. Server-side guard in class-stripe.php::ajax_create_payment_intent() rejects anything else. |
payment_mode | string | payment | "global" (default) inherits from the form-level Stripe Test/Live setting. "test" or "live" forces a per-field key — useful when one form mixes a test-mode tip-jar with a live-mode invoice. class-stripe.php::get_secret_key($field_mode) reads $_POST['payment_mode']. |
payment_description | string | payment | Free-text Stripe-only description forwarded to PaymentIntent.description for accounting clarity. It is not visitor-facing; use the normal description field for copy shown above the card element. |
styles | object | all fields | Optional per-field style config with wrapper, label, control, and description targets. Each target accepts whitelisted keys only: font_size, text_color, background_color, border_color, border_width, border_radius, padding, margin_bottom, font_weight, and safe custom_css declarations. Custom CSS supports filtered declaration-only typography, alignment, border-style, and per-side spacing (padding-, margin-) and is rendered after generated controls. |
Settings Properties Reference
| Property | Type | Default | Description |
|---|---|---|---|
submit_text | string | "Submit" | Submit button label |
success_message | string | "Thank you!" | Message after successful submission |
redirect_url | string | "" | URL to redirect after submission |
submit_action | string | "message" | One of message, redirect, or custom_js. Controls post-submit behavior after a successful AJAX submission. |
custom_js | string | "" | User-entered safe page action syntax. Parsed server-side into class add/remove/toggle actions; raw JavaScript is not executed. |
styles | object | {} | Optional form-level style config with form, submit, and message targets. Uses the same whitelist as field styles; the builder exposes Form, Submit, and Success target buttons for these global targets. |
notify_admin | boolean | true | Send admin email notification |
notify_email | string | "" | Admin notification email address |
auto_reply | boolean | false | Send auto-reply to submitter |
webhook_url | string | "" | Custom webhook URL |
css_class | string | "" | Additional CSS classes for wrapper |
multi_step | boolean | false | Enable multi-step mode |
conversational | boolean | false | Enable conversational mode |
track_abandonment | boolean | true | Per-form abandonment tracking. Default ON. When the key is absent (legacy forms), the runtime treats it as true so funnel data flows out of the box. Set to false explicitly via the Abandonment Tracking card in Form Settings to disable. Server-side gate in class-abandonment.php::ajax_save_partial(). |
track_abandonment_capture_values | boolean | false | Sub-toggle, defaults OFF. When OFF (and tracking is ON), only metadata (_metadata_only=1, _touched_fields=) is recorded. When ON, the actual partial values typed by the visitor are persisted alongside. Requires informed consent in your privacy policy. |
create_user | boolean | false | Enables PRO User Registration after a successful submission. Ignored on non-PRO licenses. |
user_email_field | string | "" | Preferred form field ID for the new user’s email address. Falls back to the first submitted email field. |
user_name_field | string | "" | Optional field ID for the user’s display name. Falls back to the first name or text field. |
user_role | string | "subscriber" | New user role. Allowed values: subscriber, contributor, author. |
Built-in admin notifications and auto-replies are rendered through Forge_Email_Template (includes/class-forge-email.php). The renderer outputs email-client-safe HTML: table layout, inline CSS, a 600px container, Outlook-safe fallback markup, mobile stacking for field/value rows, and a Form Forge blue palette. Custom extensions that send their own wp_mail() messages should use the same renderer instead of hand-rolled HTML.
Notification Field Display
Admin notifications pass submission data through FORMFORGE_Value_Formatter::submission_display_fields(), which skips internal keys and appends readable payment summary rows.
Admin Preview AJAX
The builder live preview uses the admin-only AJAX action formforge_preview_form.
| Field | Source | Notes |
|---|---|---|
nonce | formforgeAdmin.nonce | Checked with check_ajax_referer('formforge_nonce', 'nonce') |
title | string | Sanitized with sanitize_text_field() |
fields | JSON array | Sanitized before rendering; field styles pass through the style whitelist |
settings | JSON object | Sanitized before rendering; submit action and styles pass through the same server rules used on save |
conditional_logic | JSON array | Rendered into preview markup for builder feedback |
The form editor save flow surfaces wp_send_json_error() responses in the admin UI via .formforge-toast--error. This is used for license/limit failures such as the Free-plan form cap, so custom admin integrations should return a message key when blocking a save.
The endpoint requires manage_options, returns rendered HTML only, does not save the form, does not track analytics views, and does not submit data or fire integrations.
Retrieving and Modifying Forms in PHP
// Get a form (fields, settings, conditional_logic are auto-decoded)
$form = FORMFORGE_Form_Builder::instance()->get( 1 );
echo $form->title;
echo $form->fields[0]['label'];
echo $form->settings['submit_text'];
// Create a form programmatically
$new_id = FORMFORGE_Form_Builder::instance()->create( [
'title' => 'Quick Survey',
'fields' => [
[ 'id' => 'field_1', 'type' => 'rating', 'label' => 'How would you rate us?', 'required' => true, 'max_stars' => 5 ],
[ 'id' => 'field_2', 'type' => 'textarea', 'label' => 'Comments', 'required' => false, 'rows' => 4 ],
],
'settings' => [
'submit_text' => 'Send Feedback',
'success_message' => 'Thanks for your feedback!',
],
] );
// Update a form (partial update)
FORMFORGE_Form_Builder::instance()->update( $new_id, [
'title' => 'Quick Survey v2',
] );
// Delete a form
FORMFORGE_Form_Builder::instance()->delete( $new_id );—