Skip to main content
Get started with React Turnstile by following these simple steps. You’ll have a working Turnstile widget in your React application in just a few minutes.
1

Obtain Cloudflare Turnstile Keys

Before you can use React Turnstile, you need to obtain a sitekey and secret key from Cloudflare.
  1. Go to the Cloudflare Dashboard
  2. Navigate to Turnstile in the sidebar
  3. Click Add Site to create a new widget
  4. Configure your widget settings and domain
  5. Save your sitekey (for client-side) and secret key (for server-side validation)
For development and testing, you can use Cloudflare’s test keys:
  • Sitekey (always passes): 1x00000000000000000000AA
  • Secret key (always passes): 1x0000000000000000000000000000000AA
2

Install React Turnstile

Install the package using your preferred package manager:
npm install @marsidev/react-turnstile
3

Add Turnstile to Your Component

Import and use the Turnstile component in your React application:
App.tsx
import { Turnstile } from '@marsidev/react-turnstile'

function App() {
  return (
    <div>
      <h1>Contact Form</h1>
      <form>
        <input type="email" placeholder="Email" required />
        <textarea placeholder="Message" required />
        
        {/* Add Turnstile widget */}
        <Turnstile siteKey="1x00000000000000000000AA" />
        
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App
Replace 1x00000000000000000000AA with your actual Cloudflare Turnstile sitekey.
4

Handle the Verification Token

Capture the token when the user completes the challenge:
App.tsx
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'

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

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!token) {
      alert('Please complete the verification')
      return
    }

    console.log('Verification token:', token)
    // Send token to your server for validation
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" placeholder="Email" required />
      <textarea placeholder="Message" required />
      
      <Turnstile
        siteKey="1x00000000000000000000AA"
        onSuccess={(token) => {
          console.log('Success! Token:', token)
          setToken(token)
        }}
        onError={() => setToken(undefined)}
        onExpire={() => setToken(undefined)}
      />
      
      <button type="submit" disabled={!token}>
        Submit
      </button>
    </form>
  )
}
5

Validate Token on Your Server

Always validate the token on your server. Never trust client-side verification alone.
Send the token to your backend and verify it with Cloudflare:
server.ts
async function verifyTurnstileToken(token: string) {
  const response = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        secret: process.env.TURNSTILE_SECRET_KEY,
        response: token
      })
    }
  )

  const data = await response.json()
  return data.success
}

// Example API endpoint
app.post('/api/submit', async (req, res) => {
  const { token } = req.body

  const isValid = await verifyTurnstileToken(token)

  if (isValid) {
    // Process the form submission
    res.json({ success: true })
  } else {
    res.status(400).json({ error: 'Verification failed' })
  }
})
Store your Turnstile secret key in environment variables:
.env
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA

What’s Next?

You now have a working Turnstile widget! Here are some common next steps:

Component Props

Explore all available component properties

Widget Lifecycle

Understand the widget lifecycle and callbacks

Interact with Widget

Control the widget programmatically using refs

Server Validation

Learn how to validate tokens on your server

Common Customizations

Theme

Match the widget to your site’s appearance:
<Turnstile
  siteKey="1x00000000000000000000AA"
  options={{
    theme: 'dark' // or 'light', 'auto'
  }}
/>

Size

Control the widget dimensions:
<Turnstile
  siteKey="1x00000000000000000000AA"
  options={{
    size: 'compact' // or 'normal', 'flexible'
  }}
/>

Invisible Mode

For a frictionless user experience:
import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile'
import { useRef } from 'react'

function InvisibleForm() {
  const turnstileRef = useRef<TurnstileInstance>(null)

  const handleSubmit = async () => {
    // Trigger the invisible challenge
    turnstileRef.current?.execute()
    
    // Wait for the token
    const token = await turnstileRef.current?.getResponsePromise()
    console.log('Token:', token)
  }

  return (
    <div>
      <Turnstile
        ref={turnstileRef}
        siteKey="1x00000000000000000000AA"
        options={{
          execution: 'execute',
          size: 'invisible'
        }}
      />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  )
}

Need Help?

Troubleshooting

Common issues and solutions

Examples

View real-world examples

GitHub

Report issues or contribute

Cloudflare Docs

Official Turnstile documentation

Build docs developers (and LLMs) love