6.6 KiB
6.6 KiB
Configuration
Script Loading
Basic (Implicit Rendering)
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
Automatically renders widgets with class="cf-turnstile" on page load.
Explicit Rendering
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
Manual control over when/where widgets render via window.turnstile.render().
With Load Callback
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=myCallback"></script>
<script>
function myCallback() {
// API ready
window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' });
}
</script>
Compatibility Mode
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"></script>
Provides grecaptcha API for Google reCAPTCHA drop-in replacement.
Widget Configuration
Complete Options Object
{
// Required
sitekey: 'YOUR_SITE_KEY', // Widget sitekey from dashboard
// Callbacks
callback: (token) => {}, // Success - token ready
'error-callback': (code) => {}, // Error occurred
'expired-callback': () => {}, // Token expired (>5min)
'timeout-callback': () => {}, // Challenge timeout
'before-interactive-callback': () => {}, // Before showing checkbox
'after-interactive-callback': () => {}, // After user interacts
'unsupported-callback': () => {}, // Browser doesn't support Turnstile
// Appearance
theme: 'auto', // 'light' | 'dark' | 'auto'
size: 'normal', // 'normal' | 'compact' | 'flexible'
tabindex: 0, // Tab order (accessibility)
language: 'auto', // ISO 639-1 code or 'auto'
// Behavior
execution: 'render', // 'render' (auto) | 'execute' (manual)
appearance: 'always', // 'always' | 'execute' | 'interaction-only'
retry: 'auto', // 'auto' | 'never'
'retry-interval': 8000, // Retry interval (ms), default 8000
'refresh-expired': 'auto', // 'auto' | 'manual' | 'never'
// Form Integration
'response-field': true, // Add hidden input (default: true)
'response-field-name': 'cf-turnstile-response', // Hidden input name
// Analytics & Data
action: 'login', // Action name (for analytics)
cData: 'user-session-123', // Custom data (returned in siteverify)
}
Key Options Explained
execution:
'render'(default): Challenge starts immediately on render'execute': Wait forturnstile.execute()call
appearance:
'always'(default): Widget always visible'execute': Hidden untilexecute()called'interaction-only': Hidden until user interaction needed
refresh-expired:
'auto'(default): Auto-refresh expired tokens'manual': App must callreset()after expiry'never': No refresh, expired-callback triggered
retry:
'auto'(default): Auto-retry failed challenges'never': Don't retry, trigger error-callback
HTML Data Attributes
For implicit rendering, use data attributes on <div class="cf-turnstile">:
| JavaScript Property | HTML Data Attribute | Example |
|---|---|---|
sitekey |
data-sitekey |
data-sitekey="YOUR_KEY" |
action |
data-action |
data-action="login" |
cData |
data-cdata |
data-cdata="session-123" |
callback |
data-callback |
data-callback="onSuccess" |
error-callback |
data-error-callback |
data-error-callback="onError" |
expired-callback |
data-expired-callback |
data-expired-callback="onExpired" |
timeout-callback |
data-timeout-callback |
data-timeout-callback="onTimeout" |
theme |
data-theme |
data-theme="dark" |
size |
data-size |
data-size="compact" |
tabindex |
data-tabindex |
data-tabindex="0" |
response-field |
data-response-field |
data-response-field="false" |
response-field-name |
data-response-field-name |
data-response-field-name="token" |
retry |
data-retry |
data-retry="never" |
retry-interval |
data-retry-interval |
data-retry-interval="5000" |
language |
data-language |
data-language="en" |
execution |
data-execution |
data-execution="execute" |
appearance |
data-appearance |
data-appearance="interaction-only" |
refresh-expired |
data-refresh-expired |
data-refresh-expired="manual" |
Example:
<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-theme="dark"
data-callback="onTurnstileSuccess"
data-error-callback="onTurnstileError"></div>
Content Security Policy
Add these directives to CSP header/meta tag:
script-src https://challenges.cloudflare.com;
frame-src https://challenges.cloudflare.com;
Full Example:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://challenges.cloudflare.com;
frame-src https://challenges.cloudflare.com;">
Framework-Specific Setup
React
npm install @marsidev/react-turnstile
import Turnstile from '@marsidev/react-turnstile';
<Turnstile
siteKey="YOUR_SITE_KEY"
onSuccess={(token) => console.log(token)}
/>
Vue
npm install vue-turnstile
<template>
<VueTurnstile site-key="YOUR_SITE_KEY" @success="onSuccess" />
</template>
<script setup>
import VueTurnstile from 'vue-turnstile';
</script>
Svelte
npm install svelte-turnstile
<script>
import Turnstile from 'svelte-turnstile';
</script>
<Turnstile siteKey="YOUR_SITE_KEY" on:turnstile-callback={handleToken} />
Next.js (App Router)
// app/components/TurnstileWidget.tsx
'use client';
import { useEffect, useRef } from 'react';
export default function TurnstileWidget({ sitekey, onSuccess }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current && window.turnstile) {
const widgetId = window.turnstile.render(ref.current, {
sitekey,
callback: onSuccess
});
return () => window.turnstile.remove(widgetId);
}
}, [sitekey, onSuccess]);
return <div ref={ref} />;
}
Cloudflare Pages Plugin
npm install @cloudflare/pages-plugin-turnstile
// functions/_middleware.ts
import turnstilePlugin from '@cloudflare/pages-plugin-turnstile';
export const onRequest = turnstilePlugin({
secret: 'YOUR_SECRET_KEY',
onError: () => new Response('CAPTCHA failed', { status: 403 })
});