Fennec Forms docs
Setup, then the developer reference.
Getting started
Fennec Forms isn't on WordPress.org yet. Download the free plugin zip and upload it under Plugins → Add New → Upload Plugin.
Install and activate
Upload the plugin under Plugins → Add New (WordPress.org listing coming at launch) and activate it. WordPress 6.9+ and PHP 8.1+.
Create a form
Go to Fennec Forms → Add New. The new form is already a working contact form. Add fields from the grouped picker, set widths and help text, and set the form to Published.
Add it to a page
Insert the “Fennec Form” block, or paste the shortcode shown in the form list, e.g. [fennec_form id="123"].
Set up notifications
In the form’s settings, edit the admin email (default: {all_fields} plus a link to the entry) and optionally turn on a confirmation email to the person who submitted.
Optional: webhooks and CAPTCHA
Paste up to ten webhook URLs per form and copy its signing secret. Pick a CAPTCHA provider under Fennec Forms → Settings if you need one.
Verifying webhooks
Each form can post every entry as JSON to up to ten URLs, in the background after the visitor has seen the confirmation. Private and local network addresses are refused. Every request carries these headers:
X-Fennec-Signature:t=<unix time>,v1=<hex HMAC-SHA256>X-Fennec-Event,X-Fennec-Event-IdandX-Fennec-Delivery
Compute HMAC-SHA256 over <t>.<raw request body> with the form's signing secret (shown in the form builder), compare in constant time, and reject timestamps older than five minutes. The free plugin makes one attempt; Fennec Forms Pro adds retries with backoff and a delivery log.
$secret = 'your-form-signing-secret';
$body = file_get_contents( 'php://input' );
$header = $_SERVER['HTTP_X_FENNEC_SIGNATURE'] ?? ''; // t=1727600000,v1=abc…
parse_str( str_replace( ',', '&', $header ), $parts );
$expected = hash_hmac( 'sha256', $parts['t'] . '.' . $body, $secret );
$fresh = abs( time() - (int) $parts['t'] ) <= 300; // reject after 5 minutes
if ( ! $fresh || ! hash_equals( $expected, $parts['v1'] ?? '' ) ) {
http_response_code( 401 );
exit;
}import crypto from 'node:crypto';
export function verifyFennec(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}Embedding
Use the Fennec Form block, or the shortcode [fennec_form id="123"]. Forms render with theme-inheriting styles; override any --fennec-* custom property to adjust them.
Developer hooks
Extensions, including the Pro add-on, use only these public hooks. Data an inactive extension wrote is kept when an item is re-saved.
| Hook | Type | Purpose |
|---|---|---|
fennec_forms_capability | filter | Capability for forms and entries (default manage_options). |
fennec_forms_rate_limit | filter | Submissions per IP per 10 minutes (default 10). |
fennec_forms_expose_entries_to_mcp | filter | Make the list-entries ability agent-visible (default false). |
fennec_forms_field_types | filter | Register field types (Type instances keyed by key()). |
fennec_forms_sanitize_field | filter | Add extension data to a sanitized field definition. |
fennec_forms_sanitize_settings | filter | Add extension settings from the builder POST. |
fennec_forms_field_visible | filter | Whether a field takes part in a submission. |
fennec_forms_submission_values | filter | Adjust validated values before the entry is stored. |
fennec_forms_webhook_retry_delay | filter | Seconds before retrying a failed webhook (default: no retry). |
fennec_forms_after_submission | action | Fires with the entry ID, form and values. |
fennec_forms_before_entry_delete | action | Fires before an entry (and its files) is deleted. |
fennec_forms_builder_settings | action | Render extra builder settings sections. |
fennec_forms_entry_panels | action | Render extra panels on the single-entry screen. |
Suite events
Fired on fennec_event and on fennec_event/<name> with the shared envelope (id, event, plugin, occurred_at, data, site).
fennec-forms/entry-submittedfennec-forms/entry-deletedfennec-forms/form-savedfennec-forms/form-deleted
Abilities
fennec-forms/list-formsagent-visibleList forms with their fields.
fennec-forms/create-formagent-visibleDraft a new form (always saved as a draft).
fennec-forms/list-entrieshidden by defaultRead entries. Personal data, so agents can’t see it unless you opt in.
Data and uninstall
Deactivating never deletes anything. Uninstalling keeps all data unless the fennec_forms_delete_data_on_uninstall option is set first, for example with wp option update fennec_forms_delete_data_on_uninstall 1. Requires WordPress 6.9+ and PHP 8.1+.