darkghosthunter/captchavel
Captchavel gives Laravel a single, unified API for reCAPTCHA, hCaptcha, and other CAPTCHA providers. It includes validation rules, Blade directives, middleware, and fakeable testing helpers, so you can protect forms without vendor lock-in.
Read this first: Captchavel is superseded. The v7.0.3 release notes are not a changelog entry, they are an end-of-life notice. The package has been replaced by Laragear/ReCaptcha, which is described as "very similar to Captchavel, plus some great additions and performance optimizations." Treat Captchavel as maintenance-only from here: it still works, but new features, and realistically security fixes, land in the successor.
What that means in practice:
laragear/recaptcha instead. Do not adopt Captchavel.For the Captchavel workflow itself, the mental model is four steps and nothing more:
composer require laragear/captchavelphp artisan vendor:publish --tag=captchavel to get config/captchavel.php, then set the site key and secret key in .env from the Google reCAPTCHA admin console.g-recaptcha-response) alongside your form fields.FormRequest, or by calling the Captchavel facade directly.The first use case to wire up is a contact form. It is the lowest-risk place to learn the package: one form, one submit, one rule. Once you have that green, copy the pattern to registration, password reset, and login. Start by reading config/captchavel.php after publishing — the version switch (checkbox v2 / invisible v2 / v3 score-based) and the score threshold live there, and they determine how much of the rest of your integration looks.
Validation rule as the default path. The idiomatic integration is the package's rule on the response field inside a FormRequest, sitting next to your other rules. This keeps the controller thin and gives you the standard $errors bag for free. Do not reimplement token verification in a controller when the rule already exists.
Facade for non-form paths. For API endpoints, Livewire actions, or queued jobs that receive a token out of band, call the facade (Captchavel::validate($token)) and return a 422 yourself. Wrap it in a small dedicated action or service class so you have one place to swap when you migrate.
Match the config mode to the form. The three modes are not interchangeable at the markup level:
One widget per form. If a page has multiple forms (login + register in tabs), give each its own widget instance and its own response field name. Duplicate g-recaptcha-response inputs across two forms on one page is the single most common silent failure — the server reads a token generated for the other form's action and rejects it.
Layer it, don't lean on it. reCAPTCHA is an abuse signal, not authorization. Keep it stacked with Laravel's throttle middleware and your usual validation. If Google's API times out, decide explicitly whether you fail open (let the request through, log) or fail closed (reject). Fail-closed on a payment form, fail-open on a newsletter signup is a reasonable split.
Testing pattern. Do not hit Google in tests. Fake the outbound HTTP call and assert on the rule's pass/fail, and add one test that asserts a missing/blank token fails validation so you catch accidental removal of the rule. Keep a trait or helper that stubs a successful verification so every feature test that submits a protected form stays readable.
Migration pattern to Laragear/ReCaptcha. Because the successor is "very similar," the migration is mostly mechanical and can be done form-by-form rather than as a big bang:
FormRequest, run the tests for that flow.Doing it in that order means every step is independently revertible and you never have a half-migrated form in production.
The deprecation is the biggest gotcha. A package that verifies bot traffic is security-adjacent. An unmaintained one will eventually drift from Google's API or from a Laravel major. Budget the migration rather than discovering it when a Laravel upgrade breaks the service provider.
Tokens are single-use and short-lived. Roughly two minutes, one verification. Do not validate in a middleware and in a FormRequest — the second call fails and you will chase a bug that looks like "the token is invalid" when it is "the token was already spent." Also do not re-render a form after a validation failure without generating a fresh token; the user's expired token will fail on the second submit.
v3 action and score must both be checked. Verifying only the score lets a token minted for a harmless action be replayed against a sensitive one. Pin the expected action per form. And do not set the threshold at 1.0 — legitimate users will be rejected. Start around 0.5 and tune from real traffic rather than guessing.
Config cache is the classic footgun. php artisan config:cache in deploy means an .env key change does nothing until you re-cache. Symptoms: works locally, "invalid key" in staging. If reCAPTCHA suddenly rejects everything after a deploy, check the cached config before you touch the code.
Key pairing and domains. The site key and secret key are a matched pair from the same reCAPTCHA registration — mixing keys from two registrations produces generic failures. Domain restrictions on the key also mean localhost and your staging host need to be registered, or use Google's public test keys in local dev (they always pass) so you are not blocked offline.
Local and CI environments. Tests and local dev should not depend on network access to Google. Use the test keys locally and HTTP fakes in the suite. If your CI suite suddenly fails on every form submission, this is almost always the cause.
Extension points worth knowing. The config file is the main lever (mode, score, keys, badge visibility). If you need behaviour the rule does not cover — custom logging, per-route thresholds, a different failure message — bind your own rule or wrap the facade rather than editing vendor code, and do it in a single class so the eventual migration touches one file.
Badge visibility. Hiding the v3 badge is possible but Google's terms expect attribution somewhere in the UI. If you hide it, add the required text manually; otherwise you are shipping a compliance problem alongside your bot protection.
Debugging order when a submission fails. (1) Is a token actually present in the payload? (2) Is it being submitted twice? (3) Is the config cached and stale? (4) Is the site/secret pair correct for this environment? (5) Does the action/score match what the form was configured for? That order resolves nearly every report.
How can I help you explore Laravel packages today?