Skip to content

The form type

The default. CapType renders Cap’s widget: a checkbox reading “Verify you’re human”. Cap starts solving in the background as soon as the visitor moves the mouse or presses a key, so ticking it is usually instant.

$builder->add('cap', CapType::class);
<cap-widget
id="contact_cap"
data-cap-hidden-field-name="contact[cap]"
required
data-controller="silarhi--cap-bundle--cap"
data-silarhi-cap="checkbox"
data-cap-api-endpoint="https://cap.example.com/site-key/"
data-cap-lang="en"
></cap-widget>
<script type="module" src="/bundles/silarhicap/cap.js"></script>

The widget creates the hidden contact[cap] input itself and fills it once solved: the token is submitted with the other fields. While the field is required (the default), the browser refuses to submit an unticked widget and shows “Please verify you’re human”. With 'required' => false, or in a novalidate form, the bundle solves it on submit instead.

Nothing to see or click: the challenge is solved with Cap’s programmatic mode when the visitor focuses a field of the form, and on submit if they were faster.

$builder->add('cap', CapType::class, [
'mode' => CapType::MODE_INVISIBLE,
]);
<input
type="hidden"
id="contact_cap"
name="contact[cap]"
data-silarhi-cap="invisible"
data-cap-api-endpoint="https://cap.example.com/site-key/"
...
/>

Submitting waits for the challenge: the form gets aria-busy="true" meanwhile, and the cap:solving and cap:solved events let you show a spinner. Solving takes from a few hundred milliseconds to a few seconds, depending on the difficulty of the site key and the device.

Invisible mode needs the JavaScript of the bundle, the checkbox mode only needs the widget.

Option Default Description
mode 'checkbox' CapType::MODE_CHECKBOX or CapType::MODE_INVISIBLE
locale the request locale Language of the widget labels (data-cap-lang): see labels and languages
i18n [] Widget labels, keyed by the suffix of their data-cap-i18n-* attribute
worker_count null Number of solver workers (data-cap-worker-count); Cap uses one per CPU core
troubleshooting_url null The “Troubleshooting” link Cap shows a blocked visitor (data-cap-troubleshooting-url)
csp_nonce null Nonce of the script and of the widget styles: see Content Security Policy
constraints [new Cap()] Replace it to customize the messages: [new Cap(message: '…', missingMessage: '…')]
attr [] Attributes of the <cap-widget> (or of the hidden input), e.g. a class
required true Checkbox mode: the browser asks to tick the widget before submitting

mapped is false and label is false by default: the widget carries its own label.

Errors stay on the field (error_bubbling is false): form_row(form.cap) renders them under the widget.

Code Message When
Cap::MISSING_ERROR Please verify you are human. No token submitted
Cap::INVALID_ERROR The human verification failed, please try again. Cap rejected the token, or could not be reached

{{ error }} in a custom message is Cap’s reason ("Token expired", "Token not found"…) or the bundle’s (connection-failed, bad-response, invalid-json).

A token is single-use. When the form comes back with an error (any field), the widget renders again and the visitor solves a new challenge.

Cap’s widget is styled with CSS variables:

cap-widget {
--cap-background: var(--bs-body-bg);
--cap-border-color: var(--bs-border-color);
--cap-color: var(--bs-body-color);
--cap-widget-width: 100%;
}

The full list is in Cap’s documentation.