Interactive wizard for setting up Cloudflare Turnstile. Generates templates, configuration, and provides step-by-step guidance based on framework and environment.
Guides through Cloudflare Turnstile setup with framework-specific templates and validation code.
/plugin marketplace add secondsky/claude-skills/plugin install cloudflare-turnstile@claude-skillsThis interactive command guides you through complete Cloudflare Turnstile setup, from widget configuration to server-side validation.
/turnstile-setup
Question: "What widget mode do you need?"
Options:
Managed (Recommended) - Shows checkbox only when bot suspected
Invisible - No visible widget, challenge runs in background
turnstile.execute()Non-Interactive - Widget visible but no interaction needed
Output: Store selection as WIDGET_MODE
Question: "What framework are you using?"
Options:
Cloudflare Workers (Hono)
templates/turnstile-hono-route.tstemplates/wrangler-turnstile-config.jsoncReact / Next.js
templates/turnstile-react-component.tsx@marsidev/react-turnstile@1.3.1Vanilla HTML/JavaScript
templates/turnstile-widget-implicit.html (implicit rendering)templates/turnstile-widget-explicit.ts (explicit rendering)Mobile (iOS/Android/React Native/Flutter)
references/mobile-implementation.mdOutput: Store selection as FRAMEWORK
Question: "What environment is this for?"
Options:
Development (localhost)
1x00000000000000000000AA1x0000000000000000000000000000000AAStaging
Production
Output: Store selection as ENVIRONMENT
Based on selections (WIDGET_MODE, FRAMEWORK, ENVIRONMENT), generate appropriate files:
Generate wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-01-15",
"vars": {
"TURNSTILE_SITE_KEY": "${ENVIRONMENT === 'development' ? '1x00000000000000000000AA' : 'YOUR_SITE_KEY'}"
},
"env": {
"production": {
"vars": {
"TURNSTILE_SITE_KEY": "YOUR_PRODUCTION_SITE_KEY"
}
}
}
}
Generate Hono route (from templates/turnstile-hono-route.ts):
import { Hono } from 'hono'
const app = new Hono<{ Bindings: Env }>()
app.post('/api/verify', async (c) => {
const { token } = await c.req.json()
// Validate token with Turnstile Siteverify API
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
secret: c.env.TURNSTILE_SECRET_KEY,
response: token,
}),
})
const outcome = await result.json()
if (!outcome.success) {
return c.json({ error: 'Validation failed' }, 401)
}
return c.json({ success: true })
})
Generate React component (from templates/turnstile-react-component.tsx):
'use client'
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export function TurnstileWidget() {
const [token, setToken] = useState('')
return (
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
onSuccess={setToken}
options={{
theme: 'auto',
size: 'normal',
execution: '${WIDGET_MODE === 'Invisible' ? 'execute' : 'render'}',
}}
/>
)
}
Add package.json dependency:
npm install @marsidev/react-turnstile@1.3.1
Generate HTML (from templates/turnstile-widget-implicit.html):
<!DOCTYPE html>
<html>
<head>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form id="myForm" method="POST" action="/submit">
<!-- Turnstile widget (implicit rendering) -->
<div class="cf-turnstile"
data-sitekey="${ENVIRONMENT === 'development' ? '1x00000000000000000000AA' : 'YOUR_SITE_KEY'}"
data-callback="onTurnstileSuccess"
data-theme="auto"
data-size="normal">
</div>
<button type="submit">Submit</button>
</form>
<script>
function onTurnstileSuccess(token) {
console.log('Turnstile token:', token)
// Form will auto-submit with cf-turnstile-response hidden input
}
</script>
</body>
</html>
Provide instructions for dashboard configuration:
Click "Add Site" or "Add Widget"
Configure widget:
${PROJECT_NAME}-${ENVIRONMENT}example.com, localhost for dev)${WIDGET_MODE}Click "Create"
Copy Site Key and Secret Key
For Cloudflare Workers:
# Development
wrangler secret put TURNSTILE_SECRET_KEY
# Paste: 1x0000000000000000000000000000000AA (dummy secret)
# Production
wrangler secret put TURNSTILE_SECRET_KEY --env production
# Paste: [your production secret from dashboard]
For Next.js (.env.local):
NEXT_PUBLIC_TURNSTILE_SITE_KEY=YOUR_SITE_KEY
TURNSTILE_SECRET_KEY=YOUR_SECRET_KEY
For Vanilla HTML:
YOUR_SITE_KEY in HTML with actual sitekeyIn Cloudflare Dashboard → Turnstile → Widget Settings:
example.com, www.example.com, localhost (for dev)⚠️ Important: Must add exact domain. Missing domain causes Error 110200.
Generate validation code (from templates/turnstile-server-validation.ts):
interface TurnstileOutcome {
success: boolean
'error-codes': string[]
challenge_ts: string
hostname: string
action?: string
cdata?: string
}
async function validateTurnstileToken(
token: string,
secretKey: string
): Promise<TurnstileOutcome> {
const response = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
secret: secretKey,
response: token,
}),
}
)
return await response.json()
}
// Usage
const outcome = await validateTurnstileToken(token, env.TURNSTILE_SECRET_KEY)
if (!outcome.success) {
console.error('Validation failed:', outcome['error-codes'])
return new Response('Unauthorized', { status: 401 })
}
// Validation succeeded - proceed with request
Critical: Server-side validation is mandatory. Never trust client-side only.
If using Content Security Policy, add required directives:
Content-Security-Policy:
script-src 'self' https://challenges.cloudflare.com;
frame-src https://challenges.cloudflare.com;
connect-src 'self' https://challenges.cloudflare.com;
style-src 'unsafe-inline';
Verify CSP:
./scripts/check-csp.sh https://your-domain.com
If CSP errors, use /turnstile-csp-debug command or load agents/csp-debugger.md.
success: trueAlways-Pass Test:
1x00000000000000000000AA1x0000000000000000000000000000000AAAlways-Fail Test:
2x00000000000000000000AB2x0000000000000000000000000000000AAForce Interactive:
3x00000000000000000000FFBefore deploying to production, verify:
Load references/setup-checklist.md for complete 14-point verification
Quick checklist:
Use troubleshooting resources:
/turnstile-troubleshoot - Interactive error diagnosisreferences/error-codes.md - Complete error catalog (100*, 200*, 300*, 400*, 600*)references/browser-support.md - Safari 18, Brave compatibility issuesLoad references:
references/advanced-topics.md - Pre-clearance, custom actions/cdata, multi-widgetreferences/react-integration.md - React hooks, SSR, Jest testingreferences/mobile-implementation.md - iOS, Android, React Native, FlutterIf migrating from reCAPTCHA or hCaptcha:
references/migration-guide.md for step-by-step migration?compat=recaptcha parameter for drop-in reCAPTCHA replacementAfter wizard completion, you'll have:
Dashboard configuration needed:
User selections:
Generated files:
Instructions provided:
Estimated setup time: 10-15 minutes
Widget not loading:
./scripts/check-csp.shValidation failing:
Domain errors (110200):
For complete troubleshooting, use /turnstile-troubleshoot or load agents/troubleshooting-agent.md.
Wizard Duration: ~10 minutes Files Generated: 2-4 (depending on framework) Dashboard Setup: ~5 minutes Total Setup Time: ~15-20 minutes