Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/turnstile/patterns.md
1# Common Patterns23## Form Integration45### Basic Form (Implicit Rendering)67```html8<!DOCTYPE html>9<html>10<head>11<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>12</head>13<body>14<form action="/submit" method="POST">15<input type="email" name="email" required>16<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>17<button type="submit">Submit</button>18</form>19</body>20</html>21```2223### Controlled Form (Explicit Rendering)2425```javascript26<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>27<script>28let widgetId = window.turnstile.render('#container', {29sitekey: 'YOUR_SITE_KEY',30callback: (token) => console.log('Token:', token)31});3233form.addEventListener('submit', async (e) => {34e.preventDefault();35const token = window.turnstile.getResponse(widgetId);36if (!token) return;3738const response = await fetch('/submit', {39method: 'POST',40body: JSON.stringify({ 'cf-turnstile-response': token })41});4243if (!response.ok) window.turnstile.reset(widgetId);44});45</script>46```4748## Framework Patterns4950### React5152```tsx53import { useState } from 'react';54import Turnstile from '@marsidev/react-turnstile';5556export default function Form() {57const [token, setToken] = useState<string | null>(null);5859return (60<form onSubmit={async (e) => {61e.preventDefault();62if (!token) return;63await fetch('/api/submit', {64method: 'POST',65body: JSON.stringify({ 'cf-turnstile-response': token })66});67}}>68<Turnstile siteKey="YOUR_SITE_KEY" onSuccess={setToken} />69<button disabled={!token}>Submit</button>70</form>71);72}73```7475### Vue / Svelte7677```vue78<!-- Vue: npm install vue-turnstile -->79<VueTurnstile :site-key="SITE_KEY" @success="token = $event" />8081<!-- Svelte: npm install svelte-turnstile -->82<Turnstile siteKey={SITE_KEY} on:turnstile-callback={(e) => token = e.detail.token} />83```8485## Server Validation8687### Cloudflare Workers8889```typescript90interface Env {91TURNSTILE_SECRET: string;92}9394export default {95async fetch(request: Request, env: Env): Promise<Response> {96if (request.method !== 'POST') {97return new Response('Method not allowed', { status: 405 });98}99100const formData = await request.formData();101const token = formData.get('cf-turnstile-response');102103if (!token) {104return new Response('Missing token', { status: 400 });105}106107// Validate token108const ip = request.headers.get('CF-Connecting-IP');109const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {110method: 'POST',111headers: { 'Content-Type': 'application/json' },112body: JSON.stringify({113secret: env.TURNSTILE_SECRET,114response: token,115remoteip: ip116})117});118119const validation = await result.json();120121if (!validation.success) {122return new Response('CAPTCHA validation failed', { status: 403 });123}124125// Process form...126return new Response('Success');127}128};129```130131### Pages Functions132133```typescript134// functions/submit.ts - same pattern as Workers, use ctx.env and ctx.request135export const onRequestPost: PagesFunction<{ TURNSTILE_SECRET: string }> = async (ctx) => {136const token = (await ctx.request.formData()).get('cf-turnstile-response');137// Validate with ctx.env.TURNSTILE_SECRET (same as Workers pattern above)138};139```140141## Advanced Patterns142143### Pre-Clearance (Invisible)144145```html146<div id="turnstile-precheck"></div>147<form id="protected-form" style="display: none;">148<button type="submit">Submit</button>149</form>150151<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>152<script>153let cachedToken = null;154155window.onload = () => {156window.turnstile.render('#turnstile-precheck', {157sitekey: 'YOUR_SITE_KEY',158size: 'invisible',159callback: (token) => {160cachedToken = token;161document.getElementById('protected-form').style.display = 'block';162}163});164};165</script>166```167168### Token Refresh on Expiry169170```javascript171let widgetId = window.turnstile.render('#container', {172sitekey: 'YOUR_SITE_KEY',173'refresh-expired': 'manual',174'expired-callback': () => {175console.log('Token expired, refreshing...');176window.turnstile.reset(widgetId);177}178});179```180181## Testing182183### Environment-Based Keys184185```javascript186const SITE_KEY = process.env.NODE_ENV === 'production'187? 'YOUR_PRODUCTION_SITE_KEY'188: '1x00000000000000000000AA'; // Always passes189190const SECRET_KEY = process.env.NODE_ENV === 'production'191? process.env.TURNSTILE_SECRET192: '1x0000000000000000000000000000000AA';193```194