The standard integration — drop in
api.js, add a
<div class="g-recaptcha"> — was
designed for the common case of one form per page.
Push past it and the cracks appear fast, as a decade of
threads attests, from the
official reCAPTCHA group
to
Drupal's long-running multi-instance issue: second widgets that never render, both forms
sharing one g-recaptcha-response field so
the wrong token gets posted, and modals injected after
page load showing an empty box where a checkbox should
be. None of these are mysteries — they are all the
automatic-rendering convenience layer hitting its
design limit.
The Fix Is Official: Render Explicitly
Google's display documentation provides a first-class API for exactly this situation. Load the script in explicit mode with an onload callback, then render each widget yourself and keep the IDs:
<script src="https://www.google.com/recaptcha/api.js
?onload=onCaptchaReady&render=explicit"
async defer></script>
<script>
var loginWidget, contactWidget;
function onCaptchaReady() {
loginWidget = grecaptcha.render("login-captcha", {
sitekey: SITE_KEY,
});
contactWidget = grecaptcha.render("contact-captcha", {
sitekey: SITE_KEY,
});
}
</script>
Every grecaptcha.render() call returns a
widget ID — the handle the rest of
the API keys on. With IDs in hand, each form reads its
own response and resets only itself:
// On login submit: var token = grecaptcha.getResponse(loginWidget); // After the server consumed (or rejected) it: grecaptcha.reset(loginWidget); // contact form untouched
Omit the ID and both calls default to the
first widget on the page — which is precisely
why "the footer form always sends an empty token" is
the classic symptom. If your backend is logging
missing-input-response or rejecting
tokens that look fine, widget-ID confusion belongs on
the suspect list right next to the server-side issues
in our
token-debugging guide.
The Traps Between You and a Clean Multi-Widget Page
-
Rendering twice into one container.
render()throws if the element already hosts a widget ("reCAPTCHA has already been rendered in this element"). Track what you have rendered — especially in SPAs and tab UIs where initialization code can run again. -
Forms injected after load. A
modal fetched via AJAX arrives after automatic
rendering has come and gone, so its
g-recaptchadiv stays inert forever. Explicit mode solves it cleanly: callrender()right after inserting the markup. -
One token, two forms. Tokens are
single-use; "borrowing" the other form's response
— accidentally, via the shared hidden-field name —
earns a
timeout-or-duplicatefrom siteverify, the error we autopsy in the token-expiry guide. Always read the response by widget ID. -
Invisible widgets multiply the
confusion. The invisible variant binds to
buttons and fires callbacks; with several on one
page, render each explicitly and call
grecaptcha.execute(widgetId)per button. Mixing key types while refactoring earns the "Invalid key type" banner — see our key-type untangling guide. -
v3 needs none of this. Score-based
reCAPTCHA has no widgets to multiply: call
execute()per protected action with a distinctactionname (login,contact,newsletter) and verify the name server-side. If your page hosts five forms, that is five actions, not five widgets.
Step Back: Per-Form Friction Is a Choice
It is worth asking why the page needs three challenges in the first place. Each visible widget is friction billed to every legitimate visitor, and the engineering above exists to manage that friction's bookkeeping. Modern verification design pushes the other way — assess the visitor once, at the session level, and let every form on the page benefit. That is the model behavioral systems such as rCAPTCHA use, and even within Google's ecosystem, v3's action-per-flow design acknowledges the same point. Multi-widget juggling is sometimes genuinely necessary — but if you are rendering your fourth checkbox onto one page, the architecture, not the API, is the thing to question.
People Also Ask: Multi-Widget FAQ
Can I have two reCAPTCHA widgets on the same page?
Yes. Load the script with
render=explicit and an onload callback,
call grecaptcha.render() once per
container, and store each returned widget ID for
getResponse and reset calls.
Why does only my first reCAPTCHA work?
API calls without a widget ID target the first widget
rendered on the page, and forms commonly share the
default g-recaptcha-response field. Use
explicit rendering and always pass the widget ID.
How do I add reCAPTCHA to a form loaded via AJAX?
Automatic rendering only processes elements present
when the script initializes. Render the widget
manually with grecaptcha.render()
immediately after inserting the form's markup into the
DOM.
Do multiple widgets need different site keys?
No — one site key can back every widget on the page (and the site). The widget ID, not the key, distinguishes instances. Different key types (v2 checkbox vs invisible vs v3) are a different matter and cannot be mixed under one key.
Conclusion
Multiple CAPTCHAs on one page fail for an unglamorous reason: the convenience layer renders what it finds at load time and answers for one widget unless told otherwise. Explicit rendering replaces guesswork with handles — one render per container, one widget ID per form, responses and resets addressed precisely. Adopt it for any page with more than one form, render dynamic content at injection time, and spend five minutes asking whether the page really needs that many challenges at all. The bookkeeping is easy once it is explicit — it was only ever the implicit magic that hurt.
Sources & Further Reading
- Google for Developers: Displaying the widget — explicit rendering and the JavaScript API
- Google for Developers: Invisible reCAPTCHA — programmatic execute and per-widget bindings
- reCAPTCHA Google group: multiple invisible reCAPTCHA on a single page (discovery signal)
- Drupal.org: allow multiple reCAPTCHA instances on the same page (discovery signal)
- ambethia/recaptcha wiki: add multiple widgets to the same page (discovery signal)