Cloudflare Turnstile
Protect your forms with Cloudflare's low-friction CAPTCHA alternative
Cloudflare Turnstile helps protect your forms from automated spam without relying on traditional image puzzles. Static Forms supports Cloudflare Turnstile on paid plans and verifies tokens server-side using your saved secret key.
Need the strategic overview?
If you're evaluating Turnstile or want a rollout checklist, read the Cloudflare Turnstile best practices guide. If you're implementing now, this page has the exact setup details.
Paid Feature
Cloudflare Turnstile is available on Pro and Agency plans. Upgrade your plan if you want to use it in production.
Why teams choose Cloudflare Turnstile
- ✅ Minimal user interruption
- ✅ No Google dependency
- ✅ Strong default experience for modern sites
What Static Forms expects
- ✅ Cloudflare Turnstile secret key saved in dashboard
- ✅ Site key kept in your form HTML or frontend app
- ✅ Token submitted as
cf-turnstile-response
Create Your Cloudflare Turnstile Widget
Register your domains
Add the domains where your form will run, including localhost if you want to test locally.
Copy your keys
Cloudflare gives you a site key and a secret key. The site key is public and goes in your form code. The secret key stays in Static Forms settings.
Save your secret key in Static Forms
Open the form's Security tab, switch to Cloudflare Turnstile, paste your secret key, and save.
Implementation Checklist
- Create your Turnstile widget and copy both keys
- Register production, staging, preview, and localhost domains as needed
- Save only the secret key in the form's Security tab
- Add the widget with your site key in the frontend
- Confirm your form submits
cf-turnstile-response - Run a real submission test before launch
Framework setup
Turnstile does not require a React package. In React and Next.js, you load Cloudflare's script, render the widget client-side, and append cf-turnstile-response before posting to Static Forms.
Framework Examples
Choose the version that matches your stack. Each example uses the correct widget script, response field, and Static Forms submission flow.
Key Points
- Use the site key in your HTML, not the secret key
- Keep the token field name as
cf-turnstile-response - Save the secret key only in Static Forms dashboard settings
- Render with
execution: 'execute'and callturnstile.execute()from your submit handler so the token is freshly minted at submit time — Turnstile tokens expire 300s after issuance, so render-on-load can fail on slow uploads or long forms
Stable error codes for retry logic
On captcha failures, /submit returns a JSON body with a stable code field. Switch on it from your client to retry intelligently:
captcha_required— no token was sentcaptcha_expired_or_reused— token aged out or was already redeemed; refresh and retrycaptcha_failed— rejected for another reason (wrong secret, bad signature)
Environment Variables
Keep the site key in a public frontend environment variable and save the secret key only in Static Forms settings.
React Notes
- Use the explicit render script (
?render=explicit) so you control when the widget mounts - Render with
execution: 'execute'+appearance: 'interaction-only'— most users see no widget at all - Call
window.turnstile.execute(widgetId)inside your submit handler and await the callback to get a fresh token - Call
window.turnstile.remove(widgetId)in youruseEffectcleanup to avoid orphaned widgets in Cloudflare's registry
Troubleshooting
Cloudflare Turnstile token rejected
Make sure the token is submitted as cf-turnstile-response and that the secret key saved in Static Forms matches the widget site key you are using.
Widget does not load
Confirm the Cloudflare Turnstile script is loaded, your domain is allowed in Cloudflare, and any ad blockers or CSP rules are not blocking the Cloudflare challenge domain.
Compare providers
Want to compare options? See the Spam Protection overview, reCAPTCHA guide, and ALTCHA guide.