---
title: Turnstile Spin
description: Set up Turnstile end-to-end (widget + canonical server-side siteverify) from the Cloudflare dashboard or your AI coding agent.
image: https://edgetunnel-b2h.pages.dev/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://edgetunnel-b2h.pages.dev/turnstile/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Turnstile Spin

Turnstile Spin is a setup flow for Cloudflare Turnstile. It creates the widget for you, then hands your AI coding agent the materials (sitekey, secret, and a curated prompt) to embed the widget on the right forms and wire canonical server-side siteverify into your existing backend. Spin runs three ways:

* **From the Cloudflare dashboard.** Enter your domains, select **Set up**, and Spin creates the widget server-side. You receive the sitekey, the secret, and a prompt for your AI coding agent.
* **From the Wrangler CLI.** Run `wrangler turnstile widget create` to create the widget from your terminal. Wrangler prints the sitekey and secret; you wire the widget and siteverify by hand.
* **From your AI coding agent.** Paste a single prompt into Claude Code, Cursor, Codex, OpenCode, or GitHub Copilot Chat. The agent uses the inlined Spin skill to create the widget, embed it, and wire siteverify in your codebase.

All three paths produce the same widget. The only difference is where the create call runs. None of them deploy infrastructure on your behalf. Spin uses Turnstile's canonical siteverify endpoint, called from the backend you already have.

## Set up from the dashboard

1. Go to the Turnstile dashboard.  
[ Go to **Turnstile** ](https://dash.cloudflare.com/?to=/:account/turnstile)
2. Select **Set up with Spin** in the page header.
3. Enter the domains your Turnstile widget should accept tokens from. The first chip is pre-filled from your account's first active Cloudflare zone. Add more domains, or remove the pre-filled one and type any domain (Turnstile does not require a Cloudflare-managed zone). `localhost` and `127.0.0.1` are added automatically for local development.
4. Select **Set up**. Spin creates the widget and returns to a success card.
5. When setup completes, copy:

  * The **sitekey** (use as `data-sitekey` on your Turnstile widget HTML).
  * The **secret** (store as `TURNSTILE_SECRET` in your backend's env or secret manager).
  * The **agent prompt** (paste into your AI coding agent to embed the widget and add the canonical siteverify call to your existing backend handler).

If Spin fails before it finishes, the dialog shows the error and offers a fallback prompt your AI coding agent can use to drive the same setup from your editor. Select **Try again** to retry from the same dialog.

## Set up from the Wrangler CLI

If you prefer to drive setup from your terminal without an AI coding agent, use [Wrangler](https://edgetunnel-b2h.pages.dev/workers/wrangler/):

**Create a widget from Wrangler**

```sh
npx wrangler turnstile widget create "myproject" \
  --domain example.com \
  --domain localhost \
  --domain 127.0.0.1 \
  --mode managed
```

Wrangler prints the sitekey and the secret. Copy the sitekey into your widget HTML, store the secret as `TURNSTILE_SECRET` in your backend's env, and wire the canonical siteverify call as described in [Wire up the frontend](#wire-up-the-frontend).

Additional widget commands:

| Command                                                 | Purpose                                                    |
| ------------------------------------------------------- | ---------------------------------------------------------- |
| wrangler turnstile widget list                          | List every Turnstile widget on your account.               |
| wrangler turnstile widget get <sitekey>                 | Fetch a widget's configuration, including its secret.      |
| wrangler turnstile widget update <sitekey> --domain <d> | Update the domains, mode, or name of a widget.             |
| wrangler turnstile widget delete <sitekey>              | Delete a widget. Pass \-y to skip the confirmation prompt. |

All commands accept `--json` for machine-readable output. `--domain` accepts comma-separated values (`--domain a.com,b.com`) or repeated flags (`--domain a.com --domain b.com`).

## Set up from an AI coding agent

If you do not see the **Set up with Spin** button in your dashboard, or you want your agent to embed the widget and wire siteverify into your codebase in the same pass, paste this prompt into your AI coding agent:

1. **Open your AI coding agent** in your project (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot Chat).
2. **Paste this prompt into your agent:**

**Spin prompt**  
```txt  
Set up Cloudflare Turnstile in this project end to end. Plan insertion points, create the widget, embed it on the right forms, wire canonical server-side siteverify in my existing backend, and validate the integration.  
The full Turnstile Spin skill is at https://edgetunnel-b2h.pages.dev/turnstile/spin/prompt.md. Fetch it now if you do not already have it loaded.  
Domains: <DOMAINS>  
Insertion preference: <every form | only specific form>  
```  
Replace `<DOMAINS>` with your site domains (comma-separated, no spaces; include `localhost,127.0.0.1` for local development). Replace `<insertion preference>` with the form(s) or route(s) you want to protect, for example `every form`, `only the signup form`, or `only /login and /signup`.
3. **Confirm as the agent goes.** The agent checks authentication, proposes widget names, and asks you to confirm before any irreversible step.
4. **Validate.** When the agent finishes, it runs a dummy-token siteverify against `challenges.cloudflare.com` using your secret to confirm the widget and secret are wired correctly.

If you would rather install the skill locally first so the agent has it on disk:

**One-line install per agent**

```sh
# Claude Code
mkdir -p .claude/skills/turnstile-spin && \
  curl -sSL https://edgetunnel-b2h.pages.dev/turnstile/spin/prompt.md \
  -o .claude/skills/turnstile-spin/SKILL.md


# Cursor
mkdir -p .cursor/rules && \
  curl -sSL https://edgetunnel-b2h.pages.dev/turnstile/spin/prompt.md \
  -o .cursor/rules/turnstile-spin.md


# OpenCode
mkdir -p .opencode/skills/turnstile-spin && \
  curl -sSL https://edgetunnel-b2h.pages.dev/turnstile/spin/prompt.md \
  -o .opencode/skills/turnstile-spin/SKILL.md
```

Then prompt your agent: `Use the turnstile-spin skill to add Turnstile to this project.`

### What the agent does

The agent does not run silently. It detects what it can, asks only when it has to, and confirms before every irreversible step. The flow is a twelve-step wizard with several confirmation points.

| Step | What happens                                                            | Confirms with you?               |
| ---- | ----------------------------------------------------------------------- | -------------------------------- |
| 1    | Acknowledge (agent restates what it is about to do)                     | Yes                              |
| 2    | CLI check (wrangler if present; otherwise falls through to curl)        | No                               |
| 3    | Authentication (Account.Turnstile:Edit token)                           | If a token is needed             |
| 4    | Account selection (if you have more than one)                           | If more than one                 |
| 5    | Domain                                                                  | Yes                              |
| 6    | Codebase scan (frontend framework + backend handler + existing CAPTCHA) | No                               |
| 7    | Insertion plan                                                          | Yes                              |
| 8    | Widget creation (calls the Cloudflare API to create the widget)         | No (after step 7 confirms scope) |
| 9    | Embed the widget + add canonical siteverify in your existing backend    | Yes                              |
| 10   | Validate (dummy-token siteverify + widget hostname check)               | No                               |
| 11   | Persist the skill locally (so the agent can re-run on follow-up tasks)  | Yes                              |
| 12   | Final report                                                            | No                               |

If anything fails, the agent reports which step and what it tried. Most failures are recoverable by adjusting one input (token scope, domain list, insertion file) and asking the agent to resume.

## Wire up the frontend

Whichever setup path you use, Spin gives you a sitekey and a secret. The dashboard hands you a prompt for your AI coding agent. The Wrangler CLI prints them for you to wire by hand. The AI-agent setup edits your files directly.

If you set up from the dashboard and want to wire it by hand, the minimal pattern is:

**Turnstile widget on your form**

```html
<script
  src="https://challenges.cloudflare.com/turnstile/v0/api.js"
  async
  defer
></script>
<form action="/api/subscribe" method="POST">
  <input name="email" type="email" required />
  <div
    class="cf-turnstile"
    data-sitekey="YOUR_SITEKEY"
    data-action="turnstile-spin-v2"
  ></div>
  <button type="submit">Submit</button>
</form>
```

In your existing backend handler for `/api/subscribe`, call canonical siteverify and gate the rest of the handler on `success === true`:

**Canonical server-side siteverify (Node / Workers fetch idiom)**

```js
const token = request.body["cf-turnstile-response"];
const r = await fetch(
  "https://challenges.cloudflare.com/turnstile/v0/siteverify",
  {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      secret: process.env.TURNSTILE_SECRET,
      response: token,
      remoteip: request.ip,
    }),
  },
);
const { success } = await r.json();
if (!success) {
  return new Response("forbidden", { status: 403 });
}
// existing handler logic runs here, unchanged
```

Equivalent calls in other backend languages (Ruby, Python, Go, PHP) are in the per-framework references shipped with the skill. The canonical fetch idiom works inside a Cloudflare Worker the same way it does in Node; no special binding is required. The `data-action="turnstile-spin-v2"` attribute is the telemetry marker; refer to [Telemetry marker](#telemetry-marker) for the marker convention.

## Recover an existing widget

If you already have a Turnstile widget without server-side siteverify wired up, recover it from the dashboard. A banner appears above the widgets table when a widget has no matching siteverify traffic; select **Fix with Spin** to get a curated agent prompt that targets your existing widget. The prompt tells your AI coding agent to fetch the secret via API (no rotation), embed the widget on the right forms, and add the canonical siteverify call to your existing backend.

If you do not see the **Fix with Spin** banner in your dashboard, drive the same recovery from your AI coding agent directly. Paste this prompt:

**Recovery prompt**

```txt
I already have a Turnstile widget. The site key is <SITEKEY>. Use the turnstile-spin skill to wire siteverify against the existing widget: fetch the secret via the Cloudflare API (don't rotate), embed the widget on the right forms, and add canonical server-side siteverify to my existing backend.
```

The sitekey does not change. Your existing widget keeps working throughout.

## Migrate from reCAPTCHA or hCaptcha

Use the AI-agent setup for migrations. The agent detects reCAPTCHA or hCaptcha in your codebase and proposes a substitution. The substitution rules are:

* Replace script tags with `https://challenges.cloudflare.com/turnstile/v0/api.js` (`async defer`).
* Replace `class="g-recaptcha"` or `class="h-captcha"` divs with `class="cf-turnstile"`. Update `data-sitekey` to the new Turnstile site key.
* Remove any manually-added `<input type="hidden" name="g-recaptcha-response">` or `name="h-captcha-response"` elements. Turnstile renders its own hidden input named `cf-turnstile-response` automatically.
* Backend siteverify URL points at `https://challenges.cloudflare.com/turnstile/v0/siteverify`. Drop `RECAPTCHA_SECRET` or `HCAPTCHA_SECRET` env vars; add `TURNSTILE_SECRET`. The response shape is `{ success, error-codes, hostname, action, cdata }`.

Two edge cases to flag to the agent. First, reCAPTCHA v3 score thresholds do not translate: Turnstile has no score, so migrated code rejects on `success === false` rather than a numeric threshold. Second, do not auto-migrate reCAPTCHA Enterprise; refer to [the Cloudflare migration guide for reCAPTCHA](https://edgetunnel-b2h.pages.dev/turnstile/migration/recaptcha/) instead.

## Frameworks

The agent ships with frontend snippets for vanilla HTML, Next.js (App Router and Pages Router), Astro, SvelteKit, and Hugo. For other frameworks, the agent falls back to a generic vanilla-HTML pattern and asks you to confirm placement.

For Cloudflare Pages projects, the agent wires siteverify inside a Pages Function, or recommends the [Pages Plugin for Turnstile](https://edgetunnel-b2h.pages.dev/pages/functions/plugins/turnstile/) when you'd rather use a built-in plugin than write the call yourself.

For Cloudflare Workers backends, the agent writes the canonical fetch call directly into the Worker's request handler.

## Reference

### Widget configuration

| Field   | Type   | Purpose                                                       |
| ------- | ------ | ------------------------------------------------------------- |
| sitekey | string | Public identifier. Embedded in the widget HTML on every page. |
| secret  | string | Server-only. Stored as TURNSTILE\_SECRET in your backend env. |
| domains | array  | The hostnames Turnstile accepts tokens from for this widget.  |
| mode    | string | managed (default), non-interactive, or invisible.             |

### Telemetry marker

Spin-tagged widgets emit an account-level marker so Cloudflare can measure activation rates and time-to-first-siteverify for Spin-flowed widgets compared to manual ones. The marker is account-level and aggregate. No PII, no per-user tracking. Refer to the [Turnstile privacy addendum ↗](https://www.cloudflare.com/turnstile-privacy-policy/).

| data-action value | Set by                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------- |
| turnstile-spin-v2 | Current Spin flow (dashboard **Set up with Spin** \+ Wrangler CLI + AI-agent skill, all variants) |
| turnstile-spin-v1 | Legacy V1 agent flow. Preserved if encountered on an existing widget; not used for new setups.    |

Spin applies the marker automatically. If you edit the widget snippet by hand and remove the attribute, the integration still works; only the analytics segmentation is lost.

### Related

* [cloudflare/skills ↗](https://github.com/cloudflare/skills): skills bundle, includes `turnstile-spin/`
* [Turnstile server-side validation](https://edgetunnel-b2h.pages.dev/turnstile/get-started/server-side-validation/)
* [Test site keys and secrets](https://edgetunnel-b2h.pages.dev/turnstile/troubleshooting/testing/)
* [Pages Plugin for Turnstile](https://edgetunnel-b2h.pages.dev/pages/functions/plugins/turnstile/)
* [Workers secrets](https://edgetunnel-b2h.pages.dev/workers/configuration/secrets/)
* [Cloudflare Radar bot traffic ↗](https://radar.cloudflare.com/traffic/bot-classes)
* [Cloudflare Docs for Agents](https://edgetunnel-b2h.pages.dev/docs-for-agents/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://edgetunnel-b2h.pages.dev/turnstile/spin/#page","headline":"Turnstile Spin · Cloudflare Turnstile docs","description":"Set up Turnstile end-to-end (widget + canonical server-side siteverify) from the Cloudflare dashboard or your AI coding agent.","url":"https://edgetunnel-b2h.pages.dev/turnstile/spin/","inLanguage":"en","image":"https://edgetunnel-b2h.pages.dev/core-services-preview.png","dateModified":"2026-07-11","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://edgetunnel-b2h.pages.dev/#website","name":"Cloudflare Docs","url":"https://edgetunnel-b2h.pages.dev/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/turnstile/","name":"Turnstile"}},{"@type":"ListItem","position":3,"item":{"@id":"/turnstile/spin/","name":"Turnstile Spin"}}]}
```
