vue-cloudflare-turnstile
A Vue 3 component wrapper for Cloudflare Turnstile. User-friendly (re)CAPTCHA alternative
4,645
4
| Install | |
|---|---|
npm install vue-cloudflare-turnstile |
|
| Latest Version: | 1.0.3 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Nov 12, 2025 |
| Links: | GitHub · npm |
Maintainer: reinddooyeweerd
Vue Cloudflare Turnstile
A Vue 3 component wrapper for Cloudflare Turnstile. A user-friendly, privacy-preserving alternative to (re)CAPTCHA.
A working demo version of the widget can be found HERE
Features
- ✅ Vue 3 & TypeScript - Built with Vue 3 Composition API and full TypeScript support
- ✅ Easy Integration - Simple component-based API
- ✅ Fully Typed - Complete TypeScript definitions for all options and callbacks
- ✅ Flexible - Support for all Cloudflare Turnstile options
- ✅ Reactive - Automatic cleanup and lifecycle management
- ✅ SSR Compatible - Safe for server-side rendering
- ✅ Lightweight - Minimal bundle size with no unnecessary dependencies
Installation
npm install vue-cloudflare-turnstile
yarn add vue-cloudflare-turnstile
pnpm add vue-cloudflare-turnstile
Quick Start
Basic Usage
<script setup lang="ts">
import { VueTurnstile } from 'vue-cloudflare-turnstile'
const handleSuccess = (token: string) => {
console.log('Turnstile token:', token)
// Send token to your server for verification
}
</script>
<template>
<VueTurnstile
site-key="YOUR_SITE_KEY"
@success="handleSuccess"
/>
</template>
Global Registration
import { createApp } from 'vue'
import { VueTurnstilePlugin } from 'vue-cloudflare-turnstile'
import App from './App.vue'
const app = createApp(App)
// Register globally with default name 'VueTurnstile'
app.use(VueTurnstilePlugin)
// Or with a custom name
app.use(VueTurnstilePlugin, { componentName: 'Turnstile' })
app.mount('#app')
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
siteKey |
string |
required | Your Cloudflare Turnstile site key |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Widget theme |
size |
'normal' | 'compact' | 'flexible' | 'invisible' |
'normal' |
Widget size (invisible runs challenge in background with no visible widget) |
tabindex |
number |
0 |
Tab index for accessibility |
execution |
'render' | 'execute' |
'render' |
When to execute the challenge |
language |
string |
'auto' |
Language code (e.g., 'en', 'es') |
responseField |
boolean |
true |
Add response field to forms |
responseFieldName |
string |
undefined |
Custom response field name |
retry |
'auto' | 'never' |
'auto' |
Automatic retry on failure |
retryInterval |
number |
8000 |
Retry interval in milliseconds |
refreshExpired |
'auto' | 'manual' | 'never' |
'auto' |
Auto-refresh expired tokens |
appearance |
'always' | 'execute' | 'interaction-only' |
'always' |
When to show the widget |
cData |
string |
undefined |
Custom data to pass |
resetKey |
string | number |
undefined |
Change to trigger reset |
Events
| Event | Payload | Description |
|---|---|---|
@success |
token: string |
Emitted when challenge is completed successfully |
@error |
errorCode?: string |
Emitted when there is an error |
@expired |
- | Emitted when the token expires |
@beforeInteractive |
- | Emitted before the challenge is interactive |
@afterInteractive |
- | Emitted when the challenge becomes interactive |
@unsupported |
- | Emitted when the browser is unsupported |
@timeout |
- | Emitted when the challenge times out |
Methods (via Template Ref)
<script setup lang="ts">
import { ref } from 'vue'
import { VueTurnstile } from 'vue-cloudflare-turnstile'
const turnstileRef = ref<InstanceType<typeof VueTurnstile> | null>(null)
const reset = () => {
turnstileRef.value?.reset()
}
const getToken = () => {
const token = turnstileRef.value?.getResponse()
console.log('Current token:', token)
}
</script>
<template>
<VueTurnstile
ref="turnstileRef"
site-key="YOUR_SITE_KEY"
/>
</template>
| Method | Returns | Description |
|---|---|---|
reset() |
void |
Reset the widget |
remove() |
void |
Remove the widget from DOM |
getResponse() |
string | undefined |
Get the current response token |
isExpired() |
boolean |
Check if token has expired |
execute() |
Promise<string | undefined> |
Execute the widget (for execution mode 'execute') |
Usage Examples
With Form Validation
<script setup lang="ts">
import { ref } from 'vue'
import { VueTurnstile } from 'vue-cloudflare-turnstile'
const email = ref('')
const turnstileToken = ref('')
const isSubmitting = ref(false)
const handleTurnstileSuccess = (token: string) => {
turnstileToken.value = token
}
const submitForm = async () => {
if (!turnstileToken.value) {
alert('Please complete the Turnstile challenge')
return
}
isSubmitting.value = true
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email.value,
turnstileToken: turnstileToken.value
})
})
if (response.ok) {
alert('Form submitted successfully!')
}
} catch (error) {
console.error('Submission error:', error)
} finally {
isSubmitting.value = false
}
}
</script>
<template>
<form @submit.prevent="submitForm">
<input
v-model="email"
type="email"
placeholder="Email"
required
/>
<VueTurnstile
site-key="YOUR_SITE_KEY"
@success="handleTurnstileSuccess"
/>
<button
type="submit"
:disabled="!turnstileToken || isSubmitting"
>
Submit
</button>
</form>
</template>
Getting Your Site Key
- Go to the Cloudflare Dashboard
- Navigate to Turnstile
- Create a new site or select an existing one
- Copy your Site Key and Secret Key
- Use the Site Key in your frontend component
- Use the Secret Key for server-side verification
Test Keys (for development)
Cloudflare provides test keys for development:
- Visible (always passes):
1x00000000000000000000AA - Invisible (always passes):
2x00000000000000000000AB - Force challenge:
3x00000000000000000000FF
Server-Side Verification
IMPORTANT: Always verify the Turnstile token on your server. Never trust client-side validation alone.
TypeScript Support
This package includes full TypeScript definitions. Import types as needed:
import type {
TurnstileTheme,
TurnstileSize,
TurnstileErrorCode,
TurnstileProps,
TurnstileEmits
} from 'vue-cloudflare-turnstile'
Security Best Practices
- Always validate server-side - Never rely solely on client-side validation
- Use HTTPS - Turnstile requires secure connections
- Keep secrets secure - Never expose your Secret Key in client code
- Handle errors gracefully - Implement proper error handling for network issues
- Set appropriate domains - Configure allowed domains in Cloudflare Dashboard
- Monitor usage - Track Turnstile usage in your Cloudflare Dashboard
Browser Support
Turnstile works in all modern browsers that support:
- ES6+
- WebAssembly
- JavaScript enabled
Troubleshooting
Widget not appearing
- Check that you're using a valid site key
- Ensure you're accessing the page via
http://orhttps://(notfile://) - Check browser console for errors
- Verify your domain is whitelisted in Cloudflare Dashboard
Token validation failing
- Verify you're using the correct Secret Key on the server
- Ensure tokens haven't expired (300 second lifetime)
- Check that tokens aren't being reused (each token can only be validated once)
- Review error codes in the verification response
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details
Links
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.3 | ^3.3.0 | MIT | |
| 1.0.2 | ^3.3.0 | MIT | |
| 1.0.1 | ^3.3.0 | MIT | |
| 1.0.0 | ^3.3.0 | MIT |