8.7 KiB
Turnstile Widget Configuration Reference
Complete reference for all widget configuration options
Official Docs: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/widget-configurations
Widget Modes
Managed (Recommended)
Functions like a Managed Challenge Page. Selects a challenge based on visitor signals.
- Shows interactive checkbox only if bot is suspected
- Best balance of security and UX
- Use for most production deployments
Non-Interactive
Widget is displayed but visitor never needs to interact.
- No checkbox required
- Runs challenge in background
- Use for minimal user friction
Invisible
Widget is completely hidden from visitor.
- No visual presence
- Challenge runs invisibly
- Use for seamless UX, API protection
Configuration Parameters
Core Parameters
sitekey (Required)
- Type:
string - Description: Your widget's public sitekey from Cloudflare Dashboard
- Example:
data-sitekey="YOUR_SITE_KEY"or{ sitekey: 'YOUR_SITE_KEY' }
action
- Type:
string - Max Length: 32 characters
- Valid Characters:
a-z,A-Z,0-9,-,_ - Description: Custom action name tracked in analytics
- Example:
action: 'login',action: 'signup'
cdata
- Type:
string - Max Length: 255 characters
- Description: Custom data passed through to server validation
- Example:
cdata: JSON.stringify({ userId: '123' })
Appearance
theme
Controls widget color scheme.
auto(default) - Matches system preferencelight- Light modedark- Dark mode
Example:
<div class="cf-turnstile" data-theme="dark"></div>
{ theme: 'dark' }
appearance
Controls when widget becomes visible.
always(default) - Visible from page loadexecute- Visible only after challenge beginsinteraction-only- Visible only when user interaction required
Note: Only affects managed/non-interactive modes. Invisible widgets never show.
Example:
<div class="cf-turnstile" data-appearance="interaction-only"></div>
{ appearance: 'interaction-only' }
size
Controls widget dimensions.
normal(default) - 300px × 65pxcompact- 150px × 140pxflexible- 100% width, adapts to container
Example:
<div class="cf-turnstile" data-size="compact"></div>
{ size: 'flexible' }
Execution
execution
Controls when challenge runs and token is generated.
render(default) - Runs automatically after renderingexecute- Runs only whenturnstile.execute()is called
Use Cases:
render: Standard forms, immediate protectionexecute: Multi-step forms, conditional verification, performance optimization
Example:
const widgetId = turnstile.render('#container', {
sitekey: SITE_KEY,
execution: 'execute', // Manual trigger
})
// Later, when needed:
turnstile.execute(widgetId)
Callbacks
callback
Called when challenge succeeds.
Signature: (token: string) => void
Example:
<div class="cf-turnstile" data-callback="onSuccess"></div>
<script>
function onSuccess(token) {
console.log('Token:', token)
}
</script>
{
callback: (token) => {
console.log('Success:', token)
document.getElementById('submit-btn').disabled = false
}
}
error-callback
Called when challenge fails or errors occur.
Signature: (errorCode: string) => void
Example:
{
'error-callback': (error) => {
console.error('Turnstile error:', error)
showErrorMessage('Verification failed')
}
}
expired-callback
Called when token expires (after 5 minutes).
Signature: () => void
Example:
{
'expired-callback': () => {
console.warn('Token expired')
turnstile.reset(widgetId)
}
}
timeout-callback
Called when interactive challenge times out (user didn't interact).
Signature: () => void
Example:
{
'timeout-callback': () => {
console.warn('Challenge timed out')
turnstile.reset(widgetId)
}
}
Retry Behavior
retry
Controls automatic retry on errors.
auto(default) - Automatically retries on transient errorsnever- No automatic retry, manual control viaturnstile.reset()
Example:
{
retry: 'never', // Manual control
'error-callback': (error) => {
if (shouldRetry(error)) {
turnstile.reset(widgetId)
}
}
}
retry-interval
Milliseconds between automatic retries.
- Default: 8000ms (8 seconds)
- Min: 0
- Max: No limit
Example:
{
retry: 'auto',
'retry-interval': 5000, // 5 seconds
}
Complete Configuration Example
Implicit Rendering (HTML)
<div class="cf-turnstile"
data-sitekey="YOUR_SITE_KEY"
data-callback="onSuccess"
data-error-callback="onError"
data-expired-callback="onExpired"
data-timeout-callback="onTimeout"
data-theme="auto"
data-size="normal"
data-appearance="always"
data-retry="auto"
data-retry-interval="8000"
data-action="login"
data-cdata='{"userId":"123"}'>
</div>
Explicit Rendering (TypeScript)
const widgetId = turnstile.render('#container', {
sitekey: 'YOUR_SITE_KEY',
callback: (token) => console.log('Success:', token),
'error-callback': (error) => console.error('Error:', error),
'expired-callback': () => turnstile.reset(widgetId),
'timeout-callback': () => console.warn('Timeout'),
theme: 'auto',
size: 'normal',
execution: 'render',
appearance: 'always',
retry: 'auto',
'retry-interval': 8000,
action: 'login',
cdata: JSON.stringify({ userId: '123' }),
})
API Methods
turnstile.render()
Renders a widget programmatically.
Signature:
render(
container: string | HTMLElement,
options: TurnstileOptions
): string // Returns widgetId
Example:
const widgetId = turnstile.render('#my-container', {
sitekey: SITE_KEY,
callback: handleSuccess,
})
turnstile.reset()
Resets widget to initial state, clears current token.
Signature: reset(widgetId: string): void
Example:
turnstile.reset(widgetId)
turnstile.remove()
Completely removes widget from DOM.
Signature: remove(widgetId: string): void
Example:
turnstile.remove(widgetId)
turnstile.execute()
Manually triggers challenge (execution: 'execute' mode only).
Signature: execute(widgetId: string): void
Example:
document.querySelector('#submit').addEventListener('click', () => {
turnstile.execute(widgetId)
})
turnstile.getResponse()
Gets current token value.
Signature: getResponse(widgetId: string): string | undefined
Example:
const token = turnstile.getResponse(widgetId)
if (token) {
submitForm(token)
}
turnstile.isExpired()
Checks if token has expired.
Signature: isExpired(widgetId: string): boolean
Example:
if (turnstile.isExpired(widgetId)) {
turnstile.reset(widgetId)
}
Migration from reCAPTCHA
Turnstile can be a drop-in replacement for reCAPTCHA v2.
Compatibility Mode
Add ?compat=recaptcha to script URL:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"></script>
Features:
- Implicit rendering for reCAPTCHA
g-recaptcha-responseinput name- Registers API as
grecaptcha
Example:
<!-- Old reCAPTCHA code -->
<div class="g-recaptcha" data-sitekey="OLD_RECAPTCHA_KEY"></div>
<!-- New Turnstile code (compatibility mode) -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"></script>
<div class="g-recaptcha" data-sitekey="NEW_TURNSTILE_KEY"></div>
Note: Change script URL and sitekey, everything else stays the same.
Best Practices
✅ Always validate server-side - Client widget is not sufficient
✅ Handle expiration - Implement expired-callback to reset widget
✅ Handle errors - Use error-callback for user-friendly messages
✅ Use actions - Track different form types in analytics
✅ Test with dummy keys - Use 1x00000000000000000000AA for development
✅ Separate environments - Different widgets for dev/staging/production
❌ Never proxy api.js - Must load from Cloudflare CDN ❌ Never reuse tokens - Each token is single-use ❌ Never expose secret key - Keep in backend only ❌ Never skip expiration handling - Tokens expire after 5 minutes
Last Updated: 2025-10-22 API Version: v0 (stable)