Who this is for. The site builder who adds a captcha to a form, and the developer called in when a form stops submitting. Afterwards you can configure the Captcha element so failed attempts stay out of the data, place it where it works, and diagnose a captcha problem.
A captcha issue rarely shows an error, so start in the browser when diagnosing one. Every case described here was reproduced in a browser against a real form.
What the element does
The Captcha element is Google reCAPTCHA v2, in one of two modes chosen on the element:
- "I am not a robot" Checkbox. The element prints a label, Google's widget, and a hidden helper checkbox marked
required, which the widget ticks when it is solved and unticks when the answer expires. The helper is what stops the browser submitting an unsolved form. - Invisible reCAPTCHA badge. No widget, only a badge. When Google's script has loaded, the element takes over the form's
onsubmit: pressing the button runs the challenge, and only a passed challenge submits the form.
Either way the element prints its own scripts, a small inline one and Google's recaptcha/api.js, both carrying the page's nonce, so the site's script policy allows them with nothing configured. A page with three captchas loads Google's script three times; that works, but it is one more reason to have one captcha per page.
On submit, the answer travels as a field named g-recaptcha-response, and the server checks it with Google. Google accepts an answer once, and for two minutes. The reCAPTCHA keys belong to the platform's environment configuration, set by Core dna, so there is nothing to configure on the site. If the widget shows "Invalid domain for site key", the site's domain is not registered on the platform's key; raise it with Core dna.
Tick "Require completed captcha"
The element alone does not keep a failed submission out of your data. The form's Settings tab has Require completed captcha, and it is off by default:
| Setting off (the default) | Setting on | |
|---|---|---|
| A bot submits without solving the captcha | The submission is saved, as incomplete, with every answer it sent, and the uploads of any other step that passed are stored too | Nothing is saved. The page shows the captcha error; the API answers 422 |
| A visitor's captcha fails on the first submit | The step comes back with the answers in place | The step comes back with its fields empty, since nothing was saved to print back |
| Jobs on Completed | Do not start: the submission is not complete | Do not start |
| The submissions listing and exports | Fill with incomplete spam | Stay clean |
So a captcha without the setting stops spam from reaching your jobs, but not your database or your file store. Tick it on every public form that has a captcha, and put the captcha on the last step (below), so that the step a failure empties is the shortest one.
Where it belongs
On the last step, directly above the submit button. Three reasons:
- Google's answer lasts two minutes. On a JS form, every step's answers, the captcha's included, are sent together when the last step is submitted. A captcha solved on step 1 of a five-minute form has expired by then, and the visitor is on the last step with the error on a step they can no longer see.
- The answer is used up by the check. On the page-per-step model, a captcha on any step must be solved again after every failed attempt at that step, so put it where there is only one attempt left to make.
- The visitor solves it at the moment they decide to send, which is when the friction costs least.
Use one captcha per form: the server reads the answer from g-recaptcha-response whatever the element is called. And never replace the element's markup with your own: its scripts look for the ids it prints.
Use checkbox mode on a JS form. Invisible mode takes over the form's submit, and the JS model handles the same submit with its own script. The two collide in exactly the way the next section describes, every time.
The script conflicts that stop a form
Invisible mode assigns form.onsubmit when Google's script finishes loading, and Google's script is loaded async: it runs whenever it arrives. A theme's form bundle is usually loaded with defer, and runs when the page has been parsed. Which of the two runs first depends on the network, so a conflict between them can work on the developer's machine and fail for visitors. Tested against a live form with every submission intercepted:
| Your code | What happens | What the visitor sees |
|---|---|---|
Assigns form.onsubmit (for validation), and runs before Google's script | The captcha replaces it. Your validation never runs | A form that submits without your checks |
Assigns form.onsubmit, and runs after Google's script | It replaces the captcha. The form posts with an empty answer, and the server returns the captcha error | The captcha error, every time, with nothing to solve |
Adds a submit listener that sends the form with fetch | Both run. Your request goes at once with an empty answer and gets the captcha error; when the visitor solves the challenge, the captcha posts the form natively, a second request | Whatever your code shows for a failure (often nothing), then a page reload |
| Nothing; Google's script is blocked (an ad blocker, a privacy extension, a company firewall) | Invisible: onsubmit is never assigned and the form posts with no answer. Checkbox: the widget never appears, and the required helper checkbox stops the browser submitting | Invisible: the captcha error, every time. Checkbox: "Please check this box", pointing at a box behind where the widget should be. Ticking it submits the form, and the server returns the captcha error |
The rules that follow from it:
- Never assign
form.onsubmiton a form that has a captcha. Validate with the browser's own constraints (required,pattern,setCustomValidity()), which run before any submit handler and do not compete with one. - Never send a captcha form yourself. If a form must submit by script, use the JS model with checkbox mode, and let the platform's script send it.
- Say what to do when the captcha cannot load. Some visitors will never see Google's widget. Put another route in the form's own copy ("Can't send this form? Email us at…") so that a blocked script costs a visitor a few seconds, not the enquiry.
Diagnosing a form that will not submit
The symptom is no error at all, so start in the browser. Open the page, open the developer tools console, and run: