Google reCAPTCHA
Google reCAPTCHA protects your site from spam and abuse using advanced risk analysis.
Nuxt Scripts provides a registry script composable useScriptGoogleRecaptcha() to easily integrate reCAPTCHA v3 in your Nuxt app.
Nuxt Config Setup
The simplest way to load Google reCAPTCHA globally in your Nuxt App is to use Nuxt config. Alternatively you can directly use the useScriptGoogleRecaptcha composable.
export default defineNuxtConfig({
scripts: {
registry: {
googleRecaptcha: {
siteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
}
}
}
})useScriptGoogleRecaptcha()
The useScriptGoogleRecaptcha composable lets you have fine-grain control over when and how Google reCAPTCHA is loaded on your site.
const { proxy } = useScriptGoogleRecaptcha()
const token = await proxy.grecaptcha.execute(siteKey, { action: 'submit' })Please follow the Registry Scripts guide to learn more about advanced usage.
Enterprise
For reCAPTCHA Enterprise, set the enterprise option to true:
export default defineNuxtConfig({
scripts: {
registry: {
googleRecaptcha: {
siteKey: 'YOUR_SITE_KEY',
enterprise: true
}
}
}
})
China Support
For sites that need to work in China, use recaptchaNet: true to load from recaptcha.net instead of google.com:
export default defineNuxtConfig({
scripts: {
registry: {
googleRecaptcha: {
siteKey: 'YOUR_SITE_KEY',
recaptchaNet: true
}
}
}
})
Server-Side Verification
reCAPTCHA tokens must be verified on your server. Create an API endpoint to validate the token:
export default defineEventHandler(async (event) => {
const { token } = await readBody(event)
const secretKey = process.env.RECAPTCHA_SECRET_KEY
const response = await $fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: secretKey,
response: token,
}),
})
if (!response.success || response.score < 0.5) {
throw createError({
statusCode: 400,
message: 'reCAPTCHA verification failed',
})
}
return { success: true, score: response.score }
})
export default defineEventHandler(async (event) => {
const { token } = await readBody(event)
const projectId = process.env.RECAPTCHA_PROJECT_ID
const apiKey = process.env.RECAPTCHA_API_KEY
const siteKey = process.env.NUXT_PUBLIC_SCRIPTS_GOOGLE_RECAPTCHA_SITE_KEY
const response = await $fetch(
`https://recaptchaenterprise.googleapis.com/v1/projects/${projectId}/assessments?key=${apiKey}`,
{
method: 'POST',
body: {
event: { token, siteKey, expectedAction: 'submit' },
},
}
)
if (!response.tokenProperties?.valid || response.riskAnalysis?.score < 0.5) {
throw createError({
statusCode: 400,
message: 'reCAPTCHA verification failed',
})
}
return { success: true, score: response.riskAnalysis.score }
})
Hiding the Badge
reCAPTCHA v3 displays a badge in the corner of your site. You can hide it with CSS, but you must include attribution in your form:
.grecaptcha-badge { visibility: hidden; }
<p>This site is protected by reCAPTCHA and the Google
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
</p>
Test Keys
Google provides test keys for development that always pass verification. Use these for local testing:
| Key Type | Value |
|---|---|
| Site Key | 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI |
| Secret Key | 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe |
export default defineNuxtConfig({
$development: {
scripts: {
registry: {
googleRecaptcha: {
siteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
}
}
}
}
})
siteKeystring required Your reCAPTCHA site key.
enterprisebooleanUse the Enterprise version of reCAPTCHA (enterprise.js instead of api.js).
recaptchaNetbooleanUse recaptcha.net instead of google.com domain. Useful for regions where google.com is blocked.
hlstringLanguage code for the reCAPTCHA widget.
Example
Using reCAPTCHA v3 to protect a form submission with server-side verification.
<script setup lang="ts">
const { onLoaded } = useScriptGoogleRecaptcha()
const name = ref('')
const email = ref('')
const message = ref('')
const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle')
async function onSubmit() {
status.value = 'loading'
onLoaded(async ({ grecaptcha }) => {
// Get reCAPTCHA token
const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'contact' })
// Send form data + token to your API for verification
const result = await $fetch('/api/contact', {
method: 'POST',
body: {
token,
name: name.value,
email: email.value,
message: message.value
}
}).catch(() => null)
status.value = result ? 'success' : 'error'
})
}
</script>
<template>
<form @submit.prevent="onSubmit">
<input v-model="name" placeholder="Name" required>
<input v-model="email" type="email" placeholder="Email" required>
<textarea v-model="message" placeholder="Message" required />
<button type="submit" :disabled="status === 'loading'">
{{ status === 'loading' ? 'Sending...' : 'Submit' }}
</button>
<p v-if="status === 'success'">
Message sent!
</p>
<p v-if="status === 'error'">
Failed to send. Please try again.
</p>
</form>
</template>
export default defineEventHandler(async (event) => {
const { token, name, email, message } = await readBody(event)
// Verify reCAPTCHA token
const secretKey = process.env.RECAPTCHA_SECRET_KEY
const verification = await $fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: secretKey,
response: token,
}),
})
if (!verification.success || verification.score < 0.5) {
throw createError({
statusCode: 400,
message: 'reCAPTCHA verification failed',
})
}
// Process the contact form (send email, save to DB, etc.)
console.log('Contact form submitted:', { name, email, message, score: verification.score })
return { success: true }
})