Cloudflare Turnstile
Protect XPAC Forms with a scoped Turnstile widget, server-side verification, and fail-closed configuration checks.
Cloudflare Turnstile adds browser challenge protection to Forms. A protected form obtains a short-lived token in the browser, and the server verifies it with Cloudflare before accepting the submission.
Forms is required
This add-on does not register its settings, editor controls, block, or verification check unless XPAC Forms is active.
Set up Turnstile
Create a Turnstile widget in the Cloudflare dashboard. Allow every production and staging hostname that will serve a protected form.
Open XPAC → Integrations → Cloudflare Turnstile, enter the site and secret keys, choose the appearance, and save.
Edit a form, open Anti-spam protection, enable Cloudflare Turnstile, and update the form.
Optionally insert the Cloudflare Turnstile field block where the widget should appear. Without it, Forms creates a container after the Submit control.
The supported site-wide settings are:
| Setting | Default | Behaviour |
|---|---|---|
| Site Key | empty | Public key sent to the browser. |
| Secret Key | empty | Private key used only for server verification. |
| Theme | Auto | Auto, Light, or Dark. |
| Size | Normal | Normal or Compact. |
| Appearance mode | Always | Always or Interaction only. Interaction only requires a compatible Managed widget in Cloudflare. |
The secret uses a masked password control. Existing option names and stored values are unchanged; masking the field does not encrypt the WordPress option at rest.
Enabled protection fails closed when keys are missing
The per-form toggle remains available when credentials are absent. If an enabled form has a missing site or secret key, the editor shows a non-dismissible warning with a settings link, and the server refuses submissions instead of silently disabling protection.
What the visitor sees
Depending on the widget and appearance settings, Turnstile may resolve without interaction or may ask the visitor to complete a challenge. While an interactive challenge is active, the form releases its busy state so the visitor can use the widget.
The browser handles success, error, expiry, and Turnstile timeout callbacks. It also has a 30-second execution guard. Any of those failure paths clears stale tokens, returns an actionable form error, and allows a retry instead of leaving the form busy indefinitely.
Only pages that render an enabled form with configured credentials enqueue Cloudflare's script and the front-end integration. Forms inserted after initial page startup are initialized too when those assets are present.
Server verification
The server sends the token, visitor IP, and a generated idempotency key to
Cloudflare's siteverify endpoint. It accepts the submission only when the provider
reports success and the returned action and hostname match the form request:
- action must be exactly
submit; and - hostname must match the site's WordPress home or site hostname.
The verification timeout defaults to five seconds and is clamped to 1–10 seconds.
One transient transport, 429, or 5xx failure is retried with the same token and
the same UUID idempotency key, as required for a safe Turnstile retry. Permanent
failures, missing tokens, malformed responses, and action/hostname mismatches fail
closed.
Refused submissions and monitoring
A refused submission does not run notification emails or integration deliveries. Forms marks it with the detector and reason. With the default Keep submissions marked as spam setting enabled, it is quarantined in the Spam view so an administrator can review and restore a false positive. If that Forms setting is disabled, the refusal is not retained.
The Turnstile token is removed before entry storage. Operator-side failures such as
missing or invalid credentials, transport errors, malformed verdicts, and
action/hostname mismatches are written to the WordPress/PHP error log with an
XPAC cloudflare-turnstile prefix.
Test the complete production path
A CAPTCHA reduces automated abuse but cannot guarantee it. Test the real site and
secret keys from every production hostname, with both a normal browser and the
challenge path. Confirm that your privacy controls and content-security policy
allow challenges.cloudflare.com.
Privacy and availability
A protected submission contacts Cloudflare from the visitor's browser and from your server. Account for that third-party processing in your privacy and consent setup. If Cloudflare's script or verification service is blocked or unavailable, the form fails closed and asks the visitor to retry.
See Cloudflare's server-side validation guide for the provider's token and idempotency contract.
Developer reference
Site-wide values are stored in the xpac option:
| Option key | Meaning |
|---|---|
cf_turnstile_site_key | Site key |
cf_turnstile_secret_key | Secret key |
cf_turnstile_theme | Theme |
cf_turnstile_size | Size |
cf_turnstile_mode | Appearance mode |
Per-form state is stored under cloudflareCaptcha in the form's form_settings
meta:
{
"cloudflareCaptcha": {
"enable": true
}
}The optional positioning block is xp/form-field-cloudflare-turnstile. The raw
token is posted as cloudflareCAPTCHA and removed from stored entry data.
xpac_cloudflare_turnstile_request_timeout filters the server timeout before the
1–10-second clamp. xpac_forms_enable_verification can skip a provider for a
special submission flow; the filter provider slug is cloudflare-captcha.
Returning false deliberately bypasses this protection and should be narrowly
scoped. Forms Auto Submit uses that path because a signed server-driven submission
has no browser from which to obtain a CAPTCHA token.