Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Statamic Recaptcha Laravel Package

aryehraber/statamic-recaptcha

Protects Statamic forms from spam with Google reCAPTCHA v2 and v3 integration. Adds configurable captcha fields, server-side verification, and error handling to keep submissions clean.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require (or bump to ^2.8.4 if already using the package) and publish the config/views: php artisan vendor:publish --tag=captcha-config (and --tag=captcha-views if you plan to restyle). The package is auto-discovered, so no manual service provider registration is needed on Laravel 9+.
  • Look first at config/captcha.php — driver (image, math, recaptcha/turnstile), length, case sensitivity, expiry, fonts, and the image route/width/height all live there. Second stop: the package's validation rule (typically registered as captcha), since that is the contract your FormRequests will use.
  • First use case: a registration or contact form. Render the image from the package route, post the user's answer back alongside a captcha key, and validate with the package rule:
// Blade
<form method="POST" action="{{ route('contact.store') }}">
    @csrf
    <img src="{{ captcha_src() }}" alt="captcha">
    <input type="text" name="captcha" autocomplete="off">
    @error('captcha') <span class="text-red-600">{{ $message }}</span> @enderror
    <button>Send</button>
</form>
$request->validate([
    'name'    => ['required', 'string', 'max:120'],
    'email'   => ['required', 'email'],
    'message' => ['required', 'string', 'max:2000'],
    'captcha' => ['required', 'captcha'],
]);
  • v2.8.4 specifically: this patch fixes rendering/validating multiple captchas on a single page. If you have ever worked around that limitation (duplicate forms in one view, a modal login plus an inline login, a page with a comment form and a newsletter form), upgrade and delete the workaround. It is a patch release — no breaking changes, no config or migration changes. Run composer update and php artisan config:clear / view:clear after pulling.

Implementation Patterns

One captcha per form, keyed per instance. The reliable pattern for multi-form pages is to give each instance its own identifier and pass it through the request, so the validator reads the correct session entry:

// Controller / view composer — one key per form instance
$loginKey  = captcha_src('login');    // unique id scopes the session entry
$signupKey = captcha_src('signup');
// Validation — validate the answer against the matching key
$request->validate([
    'captcha' => ['required', 'captcha:' . $request->input('captcha_key')],
]);

Keep the key in a hidden input. Before 2.8.4 the keys collided and the second image on the page invalidated the first; on 2.8.4 the scoping works as intended, so prefer this over separate routes or separate pages.

FormRequest + custom message. Centralise the rule so error copy stays consistent and translatable:

class ContactRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name'        => ['required', 'string', 'max:120'],
            'email'       => ['required', 'email'],
            'captcha_key' => ['required', 'string'],
            'captcha'     => ['required', 'captcha:' . $this->input('captcha_key')],
        ];
    }

    public function messages(): array
    {
        return ['captcha.captcha' => __('validation.captcha')];
    }
}

SPA / API integration. Image-route captchas depend on the session, so decide deliberately:

  • Same-origin or Sanctum SPA: hit the captcha endpoint with credentials (withCredentials: true / axios.defaults.withCredentials = true), read the image + key from JSON, and submit both. Session cookie and CSRF token must be in place or every answer fails validation.
  • Token-based stateless clients: session-backed captchas are the wrong tool — use the reCAPTCHA/Turnstile/hCaptcha driver and verify the token server-side instead of trying to bolt sessions onto a mobile app.

Testing. Captchas make feature tests flaky. Disable or fake the driver in the test environment (CAPTCHA_DRIVER=null or a config override in phpunit.xml), or have a dedicated test that pulls the current session value and submits it. Never assert on the generated image content.

Layering with other defences. Captcha is a last line, not a first one — pair it with throttle: middleware on the POST route, a honeypot field, and time-to-submit checks. For reCAPTCHA v3, set the score threshold in config and log near-threshold rejections instead of silently failing, so you can tune it.

Customisation and extension. Publish the views to restyle without forking. Swap the generator by binding a subclass that implements the package's generator contract in a service provider ($this->app->bind(...)) — useful for branded distortion, custom fonts, or an audio alternative. Driver choice should come from config('captcha.driver') fed by env, so staging can run a no-op driver.

Gotchas and Tips

  • Session driver is the #1 failure mode. array or cookie session drivers have no server-side store, so the generated answer is never retrievable and validation always fails. Confirm SESSION_DRIVER is file/redis/database and that the image request and the POST hit the same domain, scheme, and session cookie name. Behind a load balancer without sticky sessions, use a shared session store.
  • Multiple captchas on one page: on < 2.8.4 you get cross-talk — one image's answer overwrites the other's, producing random captcha validation errors that are near-impossible to reproduce. Upgrading is the fix. After upgrading, also clear compiled config and views, otherwise the old published view/config can mask the change.
  • Never let the image route be cached. If you're behind a CDN, a proxy, or aggressive browser caching, add Cache-Control: no-store to the captcha image response, or users will be shown a stale image whose answer is no longer in session.
  • Case sensitivity and whitespace. Default configs are often case-insensitive but some drivers are not; trim input before validation and be consistent. For math drivers, accept only digits.
  • Rule ordering. bail before captcha means the captcha never runs when an earlier field fails — usually what you want (users don't get a fresh captcha for an unrelated typo), but be aware the captcha is consumed/re-rendered only on the next request, so the user may need to re-read the image after a validation round-trip. Design your form to refresh the image on 419/validation errors.
  • Debugging checklist: php artisan route:list | grep -i captcha (route registered?), inspect the session payload for the captcha key, confirm config('captcha.driver') resolves as expected after php artisan config:clear, and check that the hidden key input is actually posted (a common miss when forms are partially re-rendered by JS).
  • Accessibility. Always provide an audio/alternative path and alt text, and avoid disabling the submit button in ways that block screen-reader users from completing the form.
  • Version pin. Pin to ^2.8.4 in composer.json to guarantee the multi-instance fix across environments — composer.lock drift between dev and prod is a classic source of "works locally, always fails in prod" captcha reports.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle