Package Exports
- nuxt4-turnstile
Readme
Nuxt4 Turnstile
Cloudflare Turnstile integration for Nuxt 4 - A privacy-focused CAPTCHA alternative.
โจ Features
- ๐ Privacy-focused - No tracking, no cookies, no fingerprinting
- ๐ Nuxt 4 Compatible - Built specifically for Nuxt 4
- ๐ฆ Auto-imported - Components and composables ready to use
- ๐ก๏ธ Server Validation - Built-in server-side token verification
- ๐จ Customizable - Theme, size, appearance options
- โป๏ธ Auto-refresh - Automatically refreshes tokens before expiry
- ๐ TypeScript - Full TypeScript support
๐ฆ Installation
# npm
npm install nuxt4-turnstile
# pnpm
pnpm add nuxt4-turnstile
# bun
bun add nuxt4-turnstileโ๏ธ Configuration
Add nuxt4-turnstile to your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt4-turnstile'],
turnstile: {
siteKey: 'your-site-key', // Get from Cloudflare Dashboard
},
runtimeConfig: {
turnstile: {
// Override with NUXT_TURNSTILE_SECRET_KEY env variable
secretKey: '',
},
},
})Get Your Keys
- Go to Cloudflare Turnstile
- Create a new site
- Copy your Site Key (public) and Secret Key (server-side)
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
siteKey |
string |
'' |
Your Turnstile site key (required) |
secretKey |
string |
'' |
Your Turnstile secret key (server-side) |
addValidateEndpoint |
boolean |
false |
Add /_turnstile/validate endpoint |
appearance |
'always' | 'execute' | 'interaction-only' |
'always' |
Widget visibility |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Widget theme |
size |
'normal' | 'compact' | 'flexible' |
'normal' |
Widget size |
retry |
'auto' | 'never' |
'auto' |
Retry behavior |
retryInterval |
number |
8000 |
Retry interval in ms |
refreshExpired |
number |
250 |
Auto-refresh before expiry (seconds) |
language |
string |
'auto' |
Widget language |
๐ Usage
Component
Use the auto-imported <NuxtTurnstile> component:
<template>
<form @submit.prevent="onSubmit">
<NuxtTurnstile v-model="token" />
<button type="submit" :disabled="!token">Submit</button>
</form>
</template>
<script setup>
const token = ref('')
async function onSubmit() {
// Send token to your server for verification
await $fetch('/api/contact', {
method: 'POST',
body: { token, message: '...' }
})
}
</script>[!TIP] If you are using a form component that restricts content (like Nuxt UI Pro's
<AuthForm>), make sure to place<NuxtTurnstile>in a supported slot (e.g.,#validationor#footer) instead of the default slot.
Component Props
| Prop | Type | Description |
|---|---|---|
v-model |
string |
Two-way binding for the token |
element |
string |
HTML element to use (default: 'div') |
options |
TurnstileOptions |
Override module options |
action |
string |
Custom action for analytics |
cData |
string |
Custom data payload |
Component Events
| Event | Payload | Description |
|---|---|---|
@verify |
token: string |
Token generated |
@expire |
- | Token expired |
@error |
error: Error |
Error occurred |
@before-interactive |
- | Before challenge |
@after-interactive |
- | After challenge |
@unsupported |
- | Browser unsupported |
Component Methods (via ref)
<template>
<NuxtTurnstile ref="turnstile" v-model="token" />
<button @click="turnstile?.reset()">Reset</button>
</template>
<script setup>
const turnstile = ref()
const token = ref('')
</script>| Method | Description |
|---|---|
reset() |
Reset widget for re-verification |
remove() |
Remove widget from DOM |
getResponse() |
Get current token |
isExpired() |
Check if token is expired |
execute() |
Execute invisible challenge |
๐ก๏ธ Server Verification
Using the Built-in Endpoint
Enable the validation endpoint in your config:
export default defineNuxtConfig({
turnstile: {
siteKey: '...',
addValidateEndpoint: true,
},
})Then call it from your client:
const { success } = await $fetch('/_turnstile/validate', {
method: 'POST',
body: { token }
})Using the Helper Function
In your server routes:
// server/api/contact.post.ts
export default defineEventHandler(async (event) => {
const { token, message } = await readBody(event)
// Verify the token
const result = await verifyTurnstileToken(token)
if (!result.success) {
throw createError({
statusCode: 400,
message: 'Invalid captcha'
})
}
// Continue with your logic...
return { success: true }
})Verification Options
await verifyTurnstileToken(token, {
secretKey: 'override-secret', // Override config secret
remoteip: '1.2.3.4', // Client IP for security
action: 'login', // Validate expected action
cdata: 'user-123', // Validate expected cdata
})๐ง Composable
Use the useTurnstile composable for programmatic access:
const {
isAvailable, // Is Turnstile loaded?
siteKey, // Get site key
verify, // Verify token via endpoint
render, // Render widget programmatically
reset, // Reset widget
remove, // Remove widget
getResponse, // Get token
isExpired, // Check expiry
execute, // Execute invisible challenge
} = useTurnstile()๐ Environment Variables
Override configuration with environment variables:
NUXT_PUBLIC_TURNSTILE_SITE_KEY=your-site-key
NUXT_TURNSTILE_SECRET_KEY=your-secret-key๐ TypeScript
Types are automatically available:
import type {
TurnstileInstance,
TurnstileOptions,
TurnstileVerifyResponse,
} from 'nuxt4-turnstile'๐งช Testing
For testing, use Cloudflare's test keys:
| Key | Behavior |
|---|---|
1x00000000000000000000AA |
Always passes |
2x00000000000000000000AB |
Always blocks |
3x00000000000000000000FF |
Forces interactive challenge |
Secret test keys:
| Key | Behavior |
|---|---|
1x0000000000000000000000000000000AA |
Always passes |
2x0000000000000000000000000000000AA |
Always fails |
3x0000000000000000000000000000000AA |
Yields token spend error |
๐ License
MIT License - see LICENSE for details.