How to Validate Multiple reCAPTCHA V2 on the Same Page: Client-Side Validation Guide

In today’s digital landscape, protecting websites from spam and automated abuse is critical. Google’s reCAPTCHA V2 is a popular tool for this, using advanced risk analysis to distinguish humans from bots. While implementing a single reCAPTCHA instance is straightforward, challenges arise when multiple forms (e.g., contact, login, newsletter signup) on the same page require reCAPTCHA validation.

This guide will walk you through explicitly rendering multiple reCAPTCHA V2 widgets and implementing robust client-side validation to ensure each form submission is secure. We’ll cover common pitfalls, code examples, and testing strategies to help you seamlessly integrate multiple reCAPTCHA instances.

Table of Contents#

  1. Understanding reCAPTCHA V2
  2. Why Multiple reCAPTCHA Instances?
  3. Common Challenges with Multiple Instances
  4. Step-by-Step Implementation
  5. Client-Side Validation: Ensuring All reCAPTCHAs Are Completed
  6. Testing Your Implementation
  7. Troubleshooting Common Issues
  8. References

Understanding reCAPTCHA V2#

reCAPTCHA V2 is a security service that protects websites from spam by requiring users to complete a simple challenge (e.g., checking a box labeled “I’m not a robot”). It works by:

  • Widget Rendering: A widget is displayed on the page, prompting the user to interact.
  • Response Token: Upon successful completion, reCAPTCHA generates a unique g-recaptcha-response token.
  • Server-Side Verification: This token must be verified by your server using Google’s API to confirm the user is human.

For client-side validation, we focus on ensuring the user has completed the reCAPTCHA (i.e., a valid token exists) before allowing form submission.

Why Multiple reCAPTCHA Instances?#

You may need multiple reCAPTCHA widgets if your page contains:

  • Multiple independent forms (e.g., a contact form, login form, and newsletter signup form).
  • Dynamic content (e.g., tabs or modals with separate forms loaded conditionally).

Each form requires its own reCAPTCHA to ensure only legitimate submissions are processed.

Common Challenges with Multiple Instances#

  • ID Conflicts: Automatically rendered reCAPTCHA widgets (using class="g-recaptcha") may clash if containers share the same id.
  • Duplicate Callbacks: Shared callback functions can overwrite tokens from different widgets.
  • Validation Gaps: Failing to check all reCAPTCHA instances on submission, leading to incomplete validations.
  • Token Expiry: reCAPTCHA tokens expire after ~2 minutes. Client-side validation must account for this.

Step-by-Step Implementation#

4.1 Get reCAPTCHA Keys#

First, obtain your reCAPTCHA keys from the Google reCAPTCHA Admin Console:

  • Site Key: Public key used to render the widget on your site.
  • Secret Key: Private key used for server-side verification (keep this secure!).

Select reCAPTCHA V2 > “I’m not a robot” Checkbox and add your domain (e.g., example.com).

4.2 Add the reCAPTCHA API Script#

Include the reCAPTCHA API script in your HTML. Use async and defer to avoid blocking page load:

<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit" async defer></script>
  • onload=onRecaptchaLoad: Triggers a function (onRecaptchaLoad) once the API loads (used for explicit rendering).
  • render=explicit: Ensures widgets are rendered manually (avoids auto-rendering conflicts).

4.3 Render Multiple Widgets Explicitly#

Auto-rendering (via class="g-recaptcha") is error-prone for multiple widgets. Instead, use explicit rendering with grecaptcha.render(), which gives you full control.

Step 1: Add Widget Containers#

Create unique containers for each reCAPTCHA widget using distinct id attributes:

<!-- Form 1: Contact Form -->
<form id="contact-form">
  <input type="text" name="name" placeholder="Your Name" required>
  <div id="recaptcha-contact" class="g-recaptcha-container"></div> <!-- Unique ID -->
  <button type="submit">Submit Contact</button>
</form>
 
<!-- Form 2: Newsletter Signup -->
<form id="newsletter-form">
  <input type="email" name="email" placeholder="Your Email" required>
  <div id="recaptcha-newsletter" class="g-recaptcha-container"></div> <!-- Unique ID -->
  <button type="submit">Subscribe</button>
</form>

Step 2: Render Widgets with grecaptcha.render()#

In your JavaScript, define onRecaptchaLoad to render widgets once the API loads. Store widget IDs to reference later:

// Store widget IDs for validation later
let recaptchaWidgetIds = {};
 
// Triggered when reCAPTCHA API loads
function onRecaptchaLoad() {
  // Render Contact Form reCAPTCHA
  recaptchaWidgetIds.contact = grecaptcha.render('recaptcha-contact', {
    'sitekey': 'YOUR_SITE_KEY', // Replace with your site key
    'theme': 'light', // Optional: 'light' (default) or 'dark'
    'size': 'normal' // Optional: 'normal' (default) or 'compact'
  });
 
  // Render Newsletter Form reCAPTCHA
  recaptchaWidgetIds.newsletter = grecaptcha.render('recaptcha-newsletter', {
    'sitekey': 'YOUR_SITE_KEY', // Same site key for all widgets
    'theme': 'dark'
  });
}
  • grecaptcha.render(containerId, options): Renders a widget in the container with id=containerId.
  • Returns a widget ID (stored in recaptchaWidgetIds for later validation).

Client-Side Validation: Ensuring All reCAPTCHAs Are Completed#

Client-side validation ensures users complete the reCAPTCHA before submitting a form. Here’s how to implement it:

Use data-* attributes to link forms to their corresponding reCAPTCHA widget IDs. This makes validation scalable:

<!-- Add data-recaptcha-type to forms -->
<form id="contact-form" data-recaptcha-type="contact">...</form>
<form id="newsletter-form" data-recaptcha-type="newsletter">...</form>

Now, you can map forms to their widget IDs using data-recaptcha-type.

5.2 Validate on Form Submission#

Add a submit event listener to all forms. On submission:

  1. Prevent default form behavior.
  2. Retrieve the widget ID linked to the form.
  3. Check if the reCAPTCHA response is valid using grecaptcha.getResponse(widgetId).
  4. Proceed only if valid.
// Validate all forms on submission
document.querySelectorAll('form').forEach(form => {
  form.addEventListener('submit', function(e) {
    e.preventDefault(); // Stop form from submitting immediately
 
    // Get the reCAPTCHA type linked to this form
    const recaptchaType = this.dataset.recaptchaType;
    const widgetId = recaptchaWidgetIds[recaptchaType];
 
    // Get the reCAPTCHA response token
    const recaptchaResponse = grecaptcha.getResponse(widgetId);
 
    // Validate: Check if response is empty (incomplete)
    if (recaptchaResponse === '') {
      alert('Please complete the reCAPTCHA to submit.');
      return; // Exit if invalid
    }
 
    // If valid, submit the form (or send data via AJAX)
    alert('Form submitted successfully!');
    this.submit(); // Uncomment to submit the form
  });
});
  • grecaptcha.getResponse(widgetId): Returns the response token if the user completed the reCAPTCHA; empty string if not.

5.3 Reset reCAPTCHA After Submission#

After successful submission, reset the reCAPTCHA widget to force re-verification for subsequent submissions (e.g., if the user submits again):

// Inside the submit event listener (after validation passes)
grecaptcha.reset(widgetId); // Resets the widget

Testing Your Implementation#

Test to ensure validation works as expected:

  1. Incomplete reCAPTCHA: Submit a form without checking the reCAPTCHA box. You should see an error alert.
  2. Valid Submission: Complete the reCAPTCHA and submit. The form should proceed.
  3. Token Expiry: Wait 2+ minutes after completing the reCAPTCHA, then submit. The response token will expire, and grecaptcha.getResponse() will return an empty string (validation fails).
  4. Multiple Forms: Test all forms individually to ensure each reCAPTCHA is validated independently.

Troubleshooting Common Issues#

Widgets Not Rendering?#

  • Check API Load: Ensure onRecaptchaLoad is defined and the API script URL is correct.
  • Container IDs: Verify all widget containers have unique id attributes.
  • API Version: Use api.js (not api2.js or older versions).

Validation Fails Despite Completing reCAPTCHA?#

  • Widget ID Mismatch: Ensure recaptchaWidgetIds correctly maps forms to widget IDs.
  • Token Expiry: The user took too long to submit. Reset the widget and prompt them to re-verify.

Console Errors Like grecaptcha is not defined?#

  • Script Load Order: Ensure the reCAPTCHA script loads before your custom JavaScript. Use async defer and the onload callback to avoid race conditions.

References#

By following this guide, you can securely implement and validate multiple reCAPTCHA V2 widgets on a single page, ensuring spam-free submissions across all your forms.