Skip to main content
Scripts

Google reCAPTCHA

Google reCAPTCHA scores requests for likely spam and abuse without showing a checkbox.

useScriptGoogleRecaptcha() loads the selected client and exposes grecaptcha.

This registry integration supports score-based reCAPTCHA v3 and Enterprise flows. To render a v2 checkbox, load it separately with useScript() and follow Google's v2 display guide.

Google reCAPTCHA

View source

Nuxt Config Setup

Add this to your nuxt.config.ts to load Google reCAPTCHA globally. Alternatively you can use the useScriptGoogleRecaptcha composable for more control.

export default defineNuxtConfig({
  scripts: {
    registry: {
      googleRecaptcha: {
        siteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
        trigger: 'onNuxtReady',
      }
    }
  }
})

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
      }
    }
  }
})

Enterprise exposes its methods under grecaptcha.enterprise, so execute an action with that object rather than grecaptcha.execute:

const { onLoaded } = useScriptGoogleRecaptcha({
  siteKey: 'YOUR_SITE_KEY',
  enterprise: true,
})

onLoaded(({ grecaptcha }) => {
  grecaptcha.enterprise!.ready(async () => {
    const token = await grecaptcha.enterprise!.execute('YOUR_SITE_KEY', { action: 'submit' })
    // Send the token to your server for assessment.
  })
})

Alternative domain

Set recaptchaNet: true where google.com is unavailable. Google documents recaptcha.net as its alternative domain for global access:

export default defineNuxtConfig({
  scripts: {
    registry: {
      googleRecaptcha: {
        siteKey: 'YOUR_SITE_KEY',
        recaptchaNet: true
      }
    }
  }
})

Server-side verification

Verify every reCAPTCHA token on your server. Google recommends checking both the score and expected action, then tuning the score threshold against your own traffic rather than treating 0.5 as universal. See the reCAPTCHA v3 verification guide.

export default defineEventHandler(async (event) => {
  const { token } = await readBody(event)
  const secretKey = process.env.RECAPTCHA_SECRET_KEY
  if (!secretKey)
    throw createError({ statusCode: 500, message: 'Missing 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.action !== 'submit' || response.score < 0.5) {
    throw createError({
      statusCode: 400,
      message: 'reCAPTCHA verification failed',
    })
  }

  return { success: true, score: response.score }
})

Never expose your secret key on the client. Always verify tokens server-side.

Tokens expire after two minutes. Call execute when the user submits the protected action, not when the page loads. See Google's reCAPTCHA v3 placement guidance.

Hiding the badge

Google allows you to hide the reCAPTCHA badge if the required attribution remains visible in the user flow:

.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>

Testing

For reCAPTCHA v3, Google recommends a separate key for test environments. Scores in development may differ from production because v3 learns from real traffic. Do not use Google's published always-pass keys here; they only work with reCAPTCHA v2.

siteKeystring required

Your reCAPTCHA site key.

enterpriseboolean

Use the Enterprise version of reCAPTCHA (enterprise.js instead of api.js).

recaptchaNetboolean

Use recaptcha.net instead of google.com domain. Useful for regions where google.com is blocked.

hlstring

Language code for the reCAPTCHA widget.

Example

This example scores a contact-form submission and verifies the token on the server:

<script setup lang="ts">
const { onLoaded, onError } = useScriptGoogleRecaptcha()

const name = ref('')
const email = ref('')
const message = ref('')
const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle')

onError(() => {
  status.value = 'error'
})

function onSubmit() {
  status.value = 'loading'

  onLoaded(({ grecaptcha }) => {
    grecaptcha.ready(async () => {
      const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'contact' })

      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>