Skip to main content
ClaudeWave
Skill213 repo starsupdated 3d ago

cloudflare-turnstile

This skill should be used when the user asks to \"add turnstile\", \"implement bot protection\", \"validate turnstile token\", \"fix turnstile error\", \"setup captcha alternative\", or encounters error codes 100*/300*/600*, CSP errors, or token validation failures. Provides CAPTCHA-alternative protection for Cloudflare Workers, React, Next.js, and Hono.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/secondsky/claude-skills /tmp/cloudflare-turnstile && cp -r /tmp/cloudflare-turnstile/plugins/cloudflare-turnstile/skills/cloudflare-turnstile ~/.claude/skills/cloudflare-turnstile
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Cloudflare Turnstile

**Status**: Production Ready ✅ | **Last Verified**: 2025-11-26

**Dependencies**: None (optional: @marsidev/react-turnstile for React)

**Contents**: [Quick Start](#quick-start-10-minutes) • [Critical Rules](#critical-rules) • [Top 12 Errors](#known-issues-prevention) • [Common Patterns](#common-patterns) • [When to Load References](#when-to-load-references) • [Troubleshooting](#troubleshooting)

---

## Quick Start (10 Minutes)

### 1. Create Turnstile Widget

Get your sitekey and secret key from Cloudflare Dashboard.

```bash
# Navigate to: https://dash.cloudflare.com/?to=/:account/turnstile
# Create new widget → Copy sitekey (public) and secret key (private)
```

**Why this matters:**
- Each widget has unique sitekey/secret pair
- Sitekey goes in frontend (public)
- Secret key ONLY in backend (private)
- Use different widgets for dev/staging/production

### 2. Add Widget to Frontend

Embed the Turnstile widget in your HTML form.

```html
<!DOCTYPE html>
<html>
<head>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
  <form id="myForm" action="/submit" method="POST">
    <input type="email" name="email" required>
    <!-- Turnstile widget renders here -->
    <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
    <button type="submit">Submit</button>
  </form>
</body>
</html>
```

**CRITICAL:**
- Never proxy or cache `api.js` - must load from Cloudflare CDN
- Widget auto-creates hidden input `cf-turnstile-response` with token
- Token expires in 5 minutes
- Each token is single-use only

### 3. Validate Token on Server

ALWAYS validate the token server-side. Client-side verification alone is not secure.

```typescript
// Cloudflare Workers example
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const formData = await request.formData()
    const token = formData.get('cf-turnstile-response')
    const ip = request.headers.get('CF-Connecting-IP')

    // Validate token with Siteverify API
    const verifyFormData = new FormData()
    verifyFormData.append('secret', env.TURNSTILE_SECRET_KEY)
    verifyFormData.append('response', token)
    verifyFormData.append('remoteip', ip)

    const result = await fetch(
      'https://challenges.cloudflare.com/turnstile/v0/siteverify',
      {
        method: 'POST',
        body: verifyFormData,
      }
    )

    const outcome = await result.json()

    if (!outcome.success) {
      return new Response('Invalid Turnstile token', { status: 401 })
    }

    // Token valid - proceed with form processing
    return new Response('Success!')
  }
}
```

---

## The 3-Step Setup Process

### Step 1: Create Widget Configuration

1. Log into Cloudflare Dashboard
2. Navigate to Turnstile section
3. Click "Add Site"
4. Configure:
   - **Widget Mode**: Managed (recommended), Non-Interactive, or Invisible
   - **Domains**: Add allowed hostnames (e.g., example.com, localhost for dev)
   - **Name**: Descriptive name (e.g., "Production Login Form")

**Key Points:**
- Use separate widgets for dev/staging/production
- Restrict domains to only those you control
- Managed mode provides best balance of security and UX
- localhost must be explicitly added for local testing

### Step 2: Client-Side Integration

Choose between implicit or explicit rendering:

**Implicit Rendering** (Recommended for static forms):
```html
<!-- 1. Load script -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<!-- 2. Add widget -->
<div class="cf-turnstile"
     data-sitekey="YOUR_SITE_KEY"
     data-callback="onSuccess"
     data-error-callback="onError"></div>

<script>
function onSuccess(token) {
  console.log('Turnstile success:', token)
}

function onError(error) {
  console.error('Turnstile error:', error)
}
</script>
```

**Explicit Rendering** (For SPAs/dynamic UIs):
```typescript
// 1. Load script with explicit mode
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>

// 2. Render programmatically
const widgetId = turnstile.render('#container', {
  sitekey: 'YOUR_SITE_KEY',
  callback: (token) => {
    console.log('Token:', token)
  },
  'error-callback': (error) => {
    console.error('Error:', error)
  },
  theme: 'auto',
  execution: 'render', // or 'execute' for manual trigger
})

// Control lifecycle
turnstile.reset(widgetId)        // Reset widget
turnstile.remove(widgetId)       // Remove widget
turnstile.execute(widgetId)      // Manually trigger challenge
const token = turnstile.getResponse(widgetId) // Get current token
```

**React Integration** (using @marsidev/react-turnstile):
```tsx
import { Turnstile } from '@marsidev/react-turnstile'

export function MyForm() {
  const [token, setToken] = useState<string>()

  return (
    <form>
      <Turnstile
        siteKey={TURNSTILE_SITE_KEY}
        onSuccess={setToken}
        onError={(error) => console.error(error)}
      />
      <button disabled={!token}>Submit</button>
    </form>
  )
}
```

### Step 3: Server-Side Validation

**MANDATORY**: Always call Siteverify API to validate tokens.

```typescript
interface TurnstileResponse {
  success: boolean
  challenge_ts?: string
  hostname?: string
  error-codes?: string[]
  action?: string
  cdata?: string
}

async function validateTurnstile(
  token: string,
  secretKey: string,
  options?: {
    remoteip?: string
    idempotency_key?: string
    expectedAction?: string
    expectedHostname?: string
  }
): Promise<TurnstileResponse> {
  const formData = new FormData()
  formData.append('secret', secretKey)
  formData.append('response', token)

  if (options?.remoteip) {
    formData.append('remoteip', options.remoteip)
  }

  if (options?.idempotency_key) {
    formData.append('idempotency_key', options.idempotency_key)
  }

  const response = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
access-control-rbacSkill

Role-based access control (RBAC) with permissions and policies. Use for admin dashboards, enterprise access, multi-tenant apps, fine-grained authorization, or encountering permission hierarchies, role inheritance, policy conflicts.

aceternity-uiSkill

100+ animated React components (Aceternity UI) for Next.js with Tailwind. Use for hero sections, parallax, 3D effects, or encountering animation, shadcn CLI integration errors.

ai-elements-chatbotSkill

shadcn/ui AI chat components for conversational interfaces. Use for streaming chat, tool/function displays, reasoning visualization, or encountering Next.js App Router setup, Tailwind v4 integration, AI SDK v5 migration errors.

ai-sdk-coreSkill

Vercel AI SDK v5 for backend AI (text generation, structured output, tools, agents). Multi-provider. Use for server-side AI or encountering AI_APICallError, AI_NoObjectGeneratedError, streaming failures.

ai-sdk-uiSkill

Vercel AI SDK v5 React hooks (useChat, useCompletion, useObject) for AI chat interfaces. Use for React/Next.js AI apps or encountering parse stream errors, no response, streaming issues.

api-authenticationSkill

Secure API authentication with JWT, OAuth 2.0, API keys. Use for authentication systems, third-party integrations, service-to-service communication, or encountering token management, security headers, auth flow errors.

api-changelog-versioningSkill

Creates comprehensive API changelogs documenting breaking changes, deprecations, and migration strategies for API consumers. Use when managing API versions, communicating breaking changes, or creating upgrade guides.

api-contract-testingSkill

Verifies API contracts between services using consumer-driven contracts, schema validation, and tools like Pact. Use when testing microservices communication, preventing breaking changes, or validating OpenAPI specifications.