# Cloudflare Turnstile

> Anti-bot for every form: a CAPTCHA replacement most visitors never notice, free and unlimited. With a Worker and Resend, it's a contact form with no form service at all.

- Verdict: My default
- Vendor: Cloudflare
- Layer: [Edge](https://cloudlesslabs.com/stack/layers/edge.md). Job: Anti-bot for forms
- Last reviewed: 20 October 2026, by Nicolas Dao (Cloudless Labs, Sydney)
- Web page: https://cloudlesslabs.com/stack/cloudflare-turnstile/. The whole stack in one file: https://cloudlesslabs.com/stack/brief.md

Building a form is easy. Keeping the bots out of it is the actual job, and that's what form services really sell. Turnstile does that part for free, with no limit on challenges, and most visitors never see a puzzle.

My recipe for a contact form on a static site: the form posts to a Worker, the Worker checks the Turnstile token on the server, then sends the submission through Resend. Add a honeypot field and almost nothing gets through. It's a few dozen lines, the same for every client, and nobody else holds your visitors' messages.

## Use it when

- Any public form: contact, sign-up, sign-in, password reset, comments.
- A static site's contact form: the form posts to a Worker, which checks the Turnstile token, then emails the submission through Resend. About 40 lines, reused for every client, and the submissions never pass through a third party.
- Replacing reCAPTCHA: no image puzzles, and no Google tracking your visitors.

## Don't use it when

- Nobody can write the 40 lines: a hosted form backend such as Basin or Forminit takes the form and supports Turnstile, but every submission then sits with a third party, on a small free tier.

## Pricing

Free: unlimited challenges, up to 20 widgets per account, 10 hostnames per widget. Enterprise adds unlimited widgets and removes the branding.

## Gotchas, with fixes

- **Gotcha:** The widget alone doesn't stop bots: they post straight to your endpoint and never load it.
  **Fix:** Check every token on the server, with your secret, at https://challenges.cloudflare.com/turnstile/v0/siteverify, and reject the request unless it succeeds.
- **Gotcha:** A token can be checked only once and expires after 5 minutes, so a slow form or a retry fails.
  **Fix:** Verify once, at submission; if the form errors, reset the widget for a new token before the user resubmits.
- **Gotcha:** Some bots still get through: Turnstile tells you a browser was there, not that a human meant it.
  **Fix:** Add a hidden honeypot field and drop any submission that fills it.

## Provisioning

Official Pulumi package · Official Terraform provider

The widget is a Cloudflare resource (sitekey and secret); the secret goes into the Worker as a secret.

## Instead, consider

- [Resend](https://cloudlesslabs.com/stack/resend/index.md): Sends the form's email from the Worker.
- [Cloudflare Workers](https://cloudlesslabs.com/stack/cloudflare-workers/index.md): Receives the form, checks the token, calls Resend.

## Where it fits

- [A website, SPA or PWA](https://cloudlesslabs.com/stack/use-cases/website.md): pick 5. Any form: Turnstile keeps the bots out, a Worker checks it and Resend sends the email.

## Sources

- [Cloudflare Turnstile: plans](https://developers.cloudflare.com/turnstile/plans/)
- [Cloudflare Turnstile: server-side validation](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)
- [Cloudflare: protect your forms from spam and abuse](https://developers.cloudflare.com/use-cases/solutions/protect-sensitive-forms-fraud-abuse/)

## Related

- [Edge](https://cloudlesslabs.com/stack/layers/edge.md): every tool in this layer, with its verdict
- [The whole stack in one file](https://cloudlesslabs.com/stack/brief.md): every use case and tool, for questions that span many
- [Map of every Markdown file on cloudlesslabs.com, with what each one answers](https://ask.cloudlesslabs.com)
