reCAPTCHA
Protect your forms from spam and abuse using Google reCAPTCHA v3. Unlike v2, reCAPTCHA v3 runs in the background without user interaction, providing a seamless experience while scoring each submission for suspicious activity.
How It Works
Section titled “How It Works”- User loads your form → reCAPTCHA script generates a token in the background
- User submits form → Token is sent along with form data via
g-recaptcha-response - Forminit verifies token → Google validates the token and returns a risk score (0.0 - 1.0)
- Submission accepted or rejected → Scores below threshold are blocked
Prerequisites
Section titled “Prerequisites”- A Google account
- Access to your website’s HTML/JavaScript
Step 1: Get reCAPTCHA Keys
Section titled “Step 1: Get reCAPTCHA Keys”- Go to the Google reCAPTCHA Admin Console
- Click Create (+ icon)
- Fill in the form:
- Label: Your site name (e.g., “My Website Contact Form”)
- reCAPTCHA type: Select Score based (v3)
- Domains: Add your domain(s) (e.g.,
example.com,localhostfor testing)
- Accept the Terms of Service and click Submit
- Copy both keys:
- Site Key (public) → Used in your frontend code
- Secret Key (private) → Added to Forminit
Important: Keep your Secret Key confidential. Never expose it in client-side code.
Step 2: Add Secret Key to Forminit
Section titled “Step 2: Add Secret Key to Forminit”- Go to your Forminit Dashboard
- Select your form
- Navigate to Form Settings → CAPTCHA
- Select reCAPTCHA v3 as the provider
- Paste your Secret Key in the designated field
- Click Save
Once configured, Forminit will automatically verify the g-recaptcha-response token with Google on every submission.
Step 3: Add reCAPTCHA to Your Frontend
Section titled “Step 3: Add reCAPTCHA to Your Frontend”Load the reCAPTCHA Script
Section titled “Load the reCAPTCHA Script”Add the reCAPTCHA v3 script to your HTML, replacing YOUR_SITE_KEY with your actual site key:
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
Place this in the <head> or before your form submission logic.
Field Naming
Section titled “Field Naming”The reCAPTCHA token must be submitted with the field name g-recaptcha-response (no fi- prefix).
| Format | How to Include |
|---|---|
| FormData | formData.append('g-recaptcha-response', token) |
| JSON | Add as a text block: { type: 'text', name: 'g-recaptcha-response', value: token } |
Implementation Examples
Section titled “Implementation Examples”HTML / Static Website
Section titled “HTML / Static Website”<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
</head>
<body>
<form id="contact-form">
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<textarea name="fi-text-message" placeholder="Message" required></textarea>
<button type="submit">Send</button>
</form>
<p id="form-result"></p>
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
const SITE_KEY = 'YOUR_SITE_KEY';
const FORM_ID = 'YOUR_FORM_ID';
const forminit = new Forminit();
const form = document.getElementById('contact-form');
form.addEventListener('submit', async function(event) {
event.preventDefault();
try {
// Generate reCAPTCHA token
const token = await grecaptcha.execute(SITE_KEY, { action: 'submit' });
// Create FormData and append reCAPTCHA token (no fi- prefix)
const formData = new FormData(form);
formData.append('g-recaptcha-response', token);
// Submit to Forminit
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
document.getElementById('form-result').textContent = error.message;
return;
}
document.getElementById('form-result').textContent = 'Message sent successfully!';
form.reset();
} catch (err) {
document.getElementById('form-result').textContent = 'An error occurred. Please try again.';
console.error(err);
}
});
</script>
</body>
</html>
Next.js
Section titled “Next.js”'use client';
import { useState, useEffect } from 'react';
import { Forminit } from 'forminit';
import Script from 'next/script';
const SITE_KEY = 'YOUR_SITE_KEY';
const FORM_ID = 'YOUR_FORM_ID';
declare global {
interface Window {
grecaptcha: {
ready: (callback: () => void) => void;
execute: (siteKey: string, options: { action: string }) => Promise<string>;
};
}
}
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
const [recaptchaReady, setRecaptchaReady] = useState(false);
const forminit = new Forminit({ proxyUrl: '/api/forminit' });
useEffect(() => {
if (window.grecaptcha) {
window.grecaptcha.ready(() => setRecaptchaReady(true));
}
}, []);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!recaptchaReady) {
setError('reCAPTCHA not loaded. Please refresh the page.');
return;
}
setStatus('loading');
setError(null);
try {
// Generate reCAPTCHA token
const token = await window.grecaptcha.execute(SITE_KEY, { action: 'submit' });
// Create FormData and append token (no fi- prefix)
const form = e.currentTarget;
const formData = new FormData(form);
formData.append('g-recaptcha-response', token);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
form.reset();
} catch (err) {
setStatus('error');
setError('An error occurred. Please try again.');
}
}
return (
<>
<Script
src={`https://www.google.com/recaptcha/api.js?render=${SITE_KEY}`}
onLoad={() => {
window.grecaptcha.ready(() => setRecaptchaReady(true));
}}
/>
<form onSubmit={handleSubmit}>
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<textarea name="fi-text-message" placeholder="Message" required />
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Message sent!</p>}
<button type="submit" disabled={status === 'loading' || !recaptchaReady}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
</form>
</>
);
}
Nuxt.js
Section titled “Nuxt.js”<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Forminit } from 'forminit';
const SITE_KEY = 'YOUR_SITE_KEY';
const FORM_ID = 'YOUR_FORM_ID';
const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
const errorMessage = ref<string | null>(null);
const formRef = ref<HTMLFormElement | null>(null);
const recaptchaReady = ref(false);
const forminit = new Forminit({ proxyUrl: '/api/forminit' });
onMounted(() => {
const script = document.createElement('script');
script.src = `https://www.google.com/recaptcha/api.js?render=${SITE_KEY}`;
script.onload = () => {
window.grecaptcha.ready(() => {
recaptchaReady.value = true;
});
};
document.head.appendChild(script);
});
async function handleSubmit() {
if (!formRef.value || !recaptchaReady.value) return;
status.value = 'loading';
errorMessage.value = null;
try {
// Generate reCAPTCHA token
const token = await window.grecaptcha.execute(SITE_KEY, { action: 'submit' });
// Create FormData and append token (no fi- prefix)
const formData = new FormData(formRef.value);
formData.append('g-recaptcha-response', token);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.value = 'error';
errorMessage.value = error.message;
return;
}
status.value = 'success';
formRef.value.reset();
} catch (err) {
status.value = 'error';
errorMessage.value = 'An error occurred. Please try again.';
}
}
</script>
<template>
<form ref="formRef" @submit.prevent="handleSubmit">
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<textarea name="fi-text-message" placeholder="Message" required />
<p v-if="status === 'error'" class="error">{{ errorMessage }}</p>
<p v-if="status === 'success'" class="success">Message sent!</p>
<button type="submit" :disabled="status === 'loading' || !recaptchaReady">
{{ status === 'loading' ? 'Sending...' : 'Send' }}
</button>
</form>
</template>
JSON Submission
Section titled “JSON Submission”When using JSON format instead of FormData, include the token as a text block:
const forminit = new Forminit({ proxyUrl: '/api/forminit' });
const SITE_KEY = 'YOUR_SITE_KEY';
const FORM_ID = 'YOUR_FORM_ID';
async function submitForm() {
// Generate reCAPTCHA token
const token = await grecaptcha.execute(SITE_KEY, { action: 'submit' });
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, {
blocks: [
{
type: 'sender',
properties: {
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe',
},
},
{
type: 'text',
name: 'message',
value: 'Hello world',
},
// Include reCAPTCHA token as a text block
{
type: 'text',
name: 'g-recaptcha-response',
value: token,
},
],
});
if (error) {
console.error('Submission failed:', error.message);
return;
}
console.log('Submission successful:', data.hashId);
}
Using Actions
Section titled “Using Actions”reCAPTCHA v3 allows you to specify an “action” for each token request. This helps Google better analyze user behavior and provides more detailed analytics in your reCAPTCHA admin console.
// Use descriptive action names for different forms
const contactToken = await grecaptcha.execute(SITE_KEY, { action: 'contact_form' });
const signupToken = await grecaptcha.execute(SITE_KEY, { action: 'signup' });
const checkoutToken = await grecaptcha.execute(SITE_KEY, { action: 'checkout' });
Action naming best practices:
- Use lowercase with underscores
- Be descriptive but concise
- Use unique actions for different forms
- Avoid dynamic values in action names
Error Handling
Section titled “Error Handling”When reCAPTCHA verification fails, Forminit returns specific error codes:
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
switch (error.error) {
case 'RECAPTCHA_VERIFICATION_FAILED':
console.error('reCAPTCHA verification failed. Possible bot detected.');
break;
case 'RECAPTCHA_TOKEN_MISSING':
console.error('reCAPTCHA token was not provided.');
break;
case 'RECAPTCHA_TOKEN_EXPIRED':
console.error('reCAPTCHA token has expired. Generate a new one.');
break;
default:
console.error('Submission error:', error.message);
}
}
Common Errors
Section titled “Common Errors”| Error Code | Description | Solution |
|---|---|---|
RECAPTCHA_VERIFICATION_FAILED | Token verification failed or score too low | User may be a bot; consider showing a fallback |
RECAPTCHA_TOKEN_MISSING | g-recaptcha-response not included | Ensure token is appended to FormData or JSON blocks |
RECAPTCHA_TOKEN_EXPIRED | Token is older than 2 minutes | Generate a fresh token before submission |
RECAPTCHA_INVALID_SECRET | Secret key is incorrect | Verify secret key in Form Settings |
Token Expiration
Section titled “Token Expiration”reCAPTCHA v3 tokens expire after 2 minutes. Generate the token immediately before form submission, not when the page loads:
// ❌ Wrong - token may expire before submission
const token = await grecaptcha.execute(SITE_KEY, { action: 'submit' });
// ... user fills out form ...
formData.append('g-recaptcha-response', token); // Token might be expired
// ✅ Correct - generate token at submission time
form.addEventListener('submit', async (e) => {
e.preventDefault();
const token = await grecaptcha.execute(SITE_KEY, { action: 'submit' });
formData.append('g-recaptcha-response', token);
// Submit immediately
});
Testing Locally
Section titled “Testing Locally”To test reCAPTCHA on localhost:
- Add
localhostto your domains in the reCAPTCHA Admin Console - Use your actual site key (test keys don’t exist for v3)
- Scores on localhost may be lower than production
Tip: Monitor your reCAPTCHA analytics in the admin console to understand score distributions and adjust your threshold if needed.
Privacy Considerations
Section titled “Privacy Considerations”reCAPTCHA v3 loads Google’s scripts on your page. Consider:
- Update your privacy policy to mention reCAPTCHA usage
- Add appropriate disclosures near your forms
- Consider GDPR compliance for EU users
Example disclosure:
<p class="recaptcha-notice">
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>
Hiding the reCAPTCHA Badge
Section titled “Hiding the reCAPTCHA Badge”Google allows hiding the default badge if you include proper attribution. Add this CSS:
.grecaptcha-badge {
visibility: hidden;
}
Required: When hiding the badge, you must include visible attribution:
<p class="recaptcha-branding">
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>
Summary
Section titled “Summary”| Step | Action |
|---|---|
| 1 | Create reCAPTCHA v3 keys at Google Admin Console |
| 2 | Add Secret Key to Forminit: Form Settings → CAPTCHA |
| 3 | Load reCAPTCHA script with your Site Key |
| 4 | Generate token with grecaptcha.execute() on form submit |
| 5 | Include token as g-recaptcha-response in your submission |
Quick Reference
Section titled “Quick Reference”FormData
Section titled “FormData”const token = await grecaptcha.execute(SITE_KEY, { action: 'submit' });
formData.append('g-recaptcha-response', token);
{
blocks: [
// ... other blocks
{
type: 'text',
name: 'g-recaptcha-response',
value: token,
},
],
}
Related Documentation
Section titled “Related Documentation”- hCaptcha Integration - Alternative CAPTCHA provider
- Honeypot Protection - Additional spam protection
- Form Blocks Reference - Complete field reference
- File Uploads - File upload documentation