A type-safe, feature-rich integration of Cloudflare Turnstile for Next.js applications. This package provides both client and server-side components for seamless CAPTCHA integration.
- π Type-safe: Full TypeScript support
- π¨ Customizable: Extensive styling and behavior options
- π§ͺ Sandbox Mode: Built-in support for development testing
- β‘ Server Validation: Easy token verification
- π± Responsive: Works across all device sizes
- π Auto-reload: Configurable token refresh
- π Theme Support: Light, dark, and auto themes
- π i18n Ready: Multiple language support
# npm
npm install next-turnstile
# yarn
yarn add next-turnstile
# pnpm
pnpm add next-turnstile
# bun
bun add next-turnstile
import { Turnstile } from "next-turnstile";
function MyForm() {
const handleVerify = (token: string) => {
// Handle the verification token
console.log("Verification successful:", token);
};
return (
<Turnstile siteKey="your-site-key" onVerify={handleVerify} theme="light" />
);
}
import { validateTurnstileToken } from "next-turnstile";
async function validateToken(token: string) {
try {
const result = await validateTurnstileToken({
token,
secretKey: process.env.TURNSTILE_SECRET_KEY,
});
if (result.success) {
// Token is valid
return true;
}
} catch (error) {
console.error("Validation failed:", error);
}
return false;
}
Prop | Type | Default | Description |
---|---|---|---|
siteKey |
string |
Required | Your Cloudflare Turnstile site key |
onVerify |
(token: string) => void |
- | Callback when verification succeeds |
onError |
(error: unknown) => void |
- | Callback when an error occurs |
onExpire |
() => void |
- | Callback when the token expires |
onLoad |
() => void |
- | Callback when the widget loads |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Widget theme |
size |
'normal' | 'compact' |
'normal' |
Widget size |
appearance |
'always' | 'execute' | 'interaction-only' |
'always' |
When to show the widget |
retry |
'auto' | 'never' |
'auto' |
Retry behavior on failure |
retryInterval |
number |
8000 |
Milliseconds between retries |
refreshExpired |
'auto' | 'manual' | 'never' |
'auto' |
Token refresh behavior |
language |
string |
- | Widget language code |
id |
string |
'turnstile-widget' |
Container element ID |
className |
string |
- | Additional CSS classes |
sandbox |
boolean |
false |
Enable sandbox mode |
Option | Type | Required | Description |
---|---|---|---|
token |
string |
Yes | The token from the client |
secretKey |
string |
Yes | Your Turnstile secret key |
remoteip |
string |
No | User's IP address |
idempotencyKey |
string |
No | Unique request identifier |
sandbox |
boolean |
No | Enable sandbox mode |
import { Turnstile } from "next-turnstile";
export default function Form() {
const [token, setToken] = useState<string>();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!token) return;
const response = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify({ token }),
headers: { "Content-Type": "application/json" },
});
// Handle response...
};
return (
<form onSubmit={handleSubmit}>
<input type="email" required />
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
onVerify={setToken}
/>
<button type="submit" disabled={!token}>
Submit
</button>
</form>
);
}
import { validateTurnstileToken } from "next-turnstile";
async function submitForm(formData: FormData) {
"use server";
const token = formData.get("cf-turnstile-response");
if (!token || typeof token !== "string") {
return { error: "No token provided" };
}
const result = await validateTurnstileToken({
token,
secretKey: process.env.TURNSTILE_SECRET_KEY!,
});
if (!result.success) {
return { error: "Invalid token" };
}
// Process form submission...
}
During development, you can use sandbox mode to test without real credentials:
<Turnstile
siteKey="1x00000000000000000000AA"
sandbox={process.env.NODE_ENV === "development"}
onVerify={handleVerify}
/>
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
theme="dark"
onVerify={handleVerify}
/>
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
className="my-turnstile-widget"
onVerify={handleVerify}
/>
<style>
.my-turnstile-widget {
margin: 1rem 0;
/* Note: Internal widget styling is limited by Turnstile */
}
</style>
- Clone the repository
- Install dependencies:
pnpm install
- Start development:
pnpm dev
MIT Β© Jed Patterson
Built with β€οΈ using: