- 💪 smart verification with minimal user interaction
- 🕵️♀️ privacy-focused approach
- 💉 automatic script injection
- ⚡️ ssr ready
https://react-turnstile.vercel.app/
-
Follow these steps to obtain a free site key and secret key from Cloudflare.
-
Install
@marsidev/react-turnstile
into your React project.npm i @marsidev/react-turnstile
Or using any custom package manager:
pnpm add @marsidev/react-turnstile
yarn add @marsidev/react-turnstile
bun add @marsidev/react-turnstile
ni @marsidev/react-turnstile
The only required prop is the siteKey
.
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
return <Turnstile siteKey='1x00000000000000000000AA' />
}
Prop | Type | Description | Required |
---|---|---|---|
siteKey | string |
Your sitekey key, get one from here. | ✅ |
options | object |
Widget render options. More info about this options below. | |
scriptOptions | object |
You can customize the injected script tag with this prop. It allows you to add async , defer , nonce attributes to the script tag. You can also control whether the injected script will be added to the document body or head with appendTo attribute. |
|
onSuccess | function |
Callback that is invoked upon success of the challenge. The callback is passed a token that can be validated. | |
onExpire | function |
Callback that is invoked when a challenge expires. | |
onError | function |
Callback that is invoked when there is a network error. | |
autoResetOnExpire | boolean |
Controls whether the widget should automatically reset when it expires. If is set to true , you don't need to use the onExpire callback. Default to true . |
Option | Type | Default | Description |
---|---|---|---|
theme | string |
'auto' |
The widget theme. You can choose between light , dark or auto . |
tabIndex | number |
0 |
The tabindex of Turnstile’s iframe for accessibility purposes. |
action | string |
undefined |
A customer value that can be used to differentiate widgets under the same sitekey in analytics and which is returned upon validation. This can only contain up to 32 alphanumeric characters including _ and - . |
cData | string |
undefined |
A customer payload that can be used to attach customer data to the challenge throughout its issuance and which is returned upon validation. This can only contain up to 255 alphanumeric characters including _ and - . |
responseField | boolean |
true |
A boolean that controls if an input element with the response token is created. |
responseFieldName | string |
'cf-turnstile-response' |
Name of the input element. |
size | string |
'normal' |
The widget size. Can take the following values: 'normal' , 'compact' . The normal size is 300x65px, the compact size is 130x120px. |
retry | string |
'auto' |
Controls whether the widget should automatically retry to obtain a token if it did not succeed. The default is 'auto' , which will retry automatically. This can be set to 'never' to disable retry upon failure. |
retryInterval | number |
8000 |
When retry is set to 'auto' , retryInterval controls the time between retry attempts in milliseconds. The value must be a positive integer less than 900000 . When retry is set to 'never' , this parameter has no effect. |
All this options are optional.
Read the docs to get more info about this options.
The widget is wrapped in a
div
, so you can pass any validdiv
prop such asclassName
,id
, orstyle
.
Option | Type | Default | Description |
---|---|---|---|
nonce | string |
undefined |
Custom nonce for the injected script. |
defer | boolean |
true |
Define if set the injected script as defer. |
async | boolean |
true |
Define if set the injected script as async. |
appendTo | string |
'head' |
Define if inject the script in the head or in the body. |
id | string |
'cf-turnstile-script' |
Custom ID of the injected script. |
onLoadCallbackName | string |
'onloadTurnstileCallback' |
Custom name of the onload callback. |
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
return <Turnstile siteKey='1x00000000000000000000AA' />
}
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
return (
<Turnstile
siteKey='1x00000000000000000000AA'
className='fixed bottom-4 right-4'
options={{
action: 'submit-form',
theme: 'light',
size: 'compact'
}}
scriptOptions={{
appendTo: 'body'
}}
/>
)
}
import { useState } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
const [status, setStatus] = useState()
return (
<Turnstile
siteKey='1x00000000000000000000AA'
onError={() => setStatus('error')}
onExpire={() => setStatus('expired')}
onSuccess={() => setStatus('solved')}
/>
)
}
onExpire
does not take effect unless you setautoResetOnExpire
tofalse
.
import { useState } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
const [token, setToken] = useState()
return (
<Turnstile
siteKey='1x00000000000000000000AA'
onSuccess={(token) => setToken(token)}
/>
)
}
import { useRef } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
const ref = useRef(null)
return (
<>
<Turnstile ref={ref} siteKey='1x00000000000000000000AA'/>
<button onClick={() => alert(ref.current?.getResponse())}>
Get response
</button>
<button onClick={() => ref.current?.reset()}>
Reset widget
</button>
<button onClick={() => ref.current?.remove()}>
Remove widget
</button>
<button onClick={() => ref.current?.render()}>
Render widget
</button>
</>
)
}
import { useRef } from 'react'
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'
function Widget() {
const ref = useRef<TurnstileInstance>(null)
return (
<>
<Turnstile ref={ref} siteKey='1x00000000000000000000AA'/>
<button onClick={() => alert(ref.current?.getResponse())}>
Get response
</button>
</>
)
}
// LoginForm.jsx
import { useRef } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'
export default function LoginForm() {
const formRef = useRef(null)
async function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(formRef.current)
const token = formData.get('cf-turnstile-response')
const res = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify({ token }),
headers: {
'content-type': 'application/json'
}
})
const data = await res.json()
if (data.success) {
// the token has been validated
}
}
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" placeholder="username"/>
<input type="password" placeholder="password"/>
<Turnstile siteKey='1x00000000000000000000AA'/>
<button type='submit'>Login</button>
</form>
)
}
// `pages/api/verify.js`
// this is an example of a next.js api route
// this code runs on the server
const endpoint = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const secret = '1x0000000000000000000000000000000AA'
export default async function handler(request, response) {
const body = `secret=${encodeURIComponent(secret)}&response=${encodeURIComponent(request.body.token)}`
const res = await fetch(endpoint, {
method: 'POST',
body,
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
})
const data = await res.json()
return response.json(data)
}
Check the demo and his source code to see a code similar to the above in action.
Check the docs for more info about server side validation.
As you might noted, there is three ways to get the token response from a solved challenge:
- by catching it from the
onSuccess
callback.- by calling the
.getResponse()
method.- by reading the widget response input with name
cf-turnstile-response
. This one is not an option if you setoptions.fieldResponse
tofalse
.
By default, you don't need to handle the widget expiring, unless you set
autoResetOnExpire
tofalse
.
import { useRef } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'
function Widget() {
const ref = useRef(null)
return (
<Turnstile
ref={ref}
autoResetOnExpire={false}
siteKey='1x00000000000000000000AA'
onExpire={() => ref.current?.reset()}
/>
)
}
Any contributions are greatly appreciated. If you have a suggestion that would make this project better, please fork the repo and create a Pull Request. You can also open an issue.
- Fork or clone this repository.
- Install pnpm.
- Install dependencies with
pnpm install
. - You can use
pnpm dev
to start the demo page in development mode, which also rebuild the library when file changes are detected in thesrc
folder. - You also can use
pnpm stub
, which rununbuild --stub
, a passive watcher to use the library while developing without needing to watch and rebuild. However, this option can't be used in an esm context.
Inspired by
Published under the MIT License.