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

Laravel Google Captcha Laravel Package

buzz/laravel-google-captcha

Laravel integration for Google reCAPTCHA v2 and v3, adding validation rules, Blade directives and helpers to render and verify captchas. Configurable site/secret keys, score thresholds and error messages help protect forms from spam and bots with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer and let package discovery do its job on Laravel 5.5+:

composer require buzz/laravel-google-captcha

Publish the config file (the provider class is Buzz\LaravelGoogleCaptcha\CaptchaServiceProvider):

php artisan vendor:publish --provider="Buzz\LaravelGoogleCaptcha\CaptchaServiceProvider" --tag=config

Add your Google reCAPTCHA credentials to .env. Grab them from https://www.google.com/recaptcha/admin — this is the first place to look if anything silently fails, because the package does no sanity checking on missing keys:

CAPTCHA_SITEKEY=your-site-key
CAPTCHA_SECRET=your-secret-key

The canonical first use case is a contact form. In the Blade view:

<form method="POST" action="/contact">
    @csrf
    {!! Captcha::render() !!}
    <button type="submit">Send</button>
</form>

In the controller (or better, a FormRequest):

$this->validate($request, [
    'g-recaptcha-response' => 'required|captcha',
]);

Note the rule is attached to the g-recaptcha-response field — not to a field named captcha. That single line is where most first attempts break.

Implementation Patterns

FormRequest encapsulation. The rule is a standard Laravel validation rule, so push it into a FormRequest and keep controllers clean:

class ContactRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name'    => 'required|string',
            'email'   => 'required|email',
            'message' => 'required|string',
            'g-recaptcha-response' => 'required|captcha',
        ];
    }

    public function messages(): array
    {
        return [
            'g-recaptcha-response.required' => 'Please complete the captcha.',
            'g-recaptcha-response.captcha'  => 'Captcha verification failed, try again.',
        ];
    }
}

Middleware for route groups. If multiple public forms need protection, wrap the rule in a middleware that inspects g-recaptcha-response and throws a ValidationException — this keeps controllers and requests unchanged:

class VerifyCaptcha
{
    public function handle($request, Closure $next)
    {
        validator(
            ['g-recaptcha-response' => $request->input('g-recaptcha-response')],
            ['g-recaptcha-response' => 'required|captcha']
        )->validate();

        return $next($request);
    }
}

Then Route::middleware(VerifyCaptcha::class)->group(...) around the public POST routes.

Manual verification with the facade. When the rule doesn't fit — e.g. an Ajax endpoint returning JSON, or you want the failure reason — call the verifier directly. The facade is Buzz\LaravelGoogleCaptcha\Facades\Captcha:

if (! Captcha::verify(request('g-recaptcha-response'))) {
    return response()->json(['error' => 'captcha_failed'], 422);
}

This is the pattern to reach for when you want to log failure codes or gate on a v3 score threshold before accepting the request.

Blade rendering options. Captcha::render() reads the attributes array from config/captcha.php. To theme a widget differently on one page, override inline rather than editing the shared config:

{!! Captcha::render() !!} {{-- uses config defaults --}}

{{-- or push a different theme for a single form --}}
{!! Captcha::render(['theme' => 'dark']) !!}

v3 / score-based flows. The wrapper is a thin server-side verifier. For v3 you must (a) include the Google JS with your site key, (b) execute it with an action name on the client, and (c) decide on a score threshold on the server. The package handles only the final siteverify call — score checks, action matching, and the JS wiring are on you. For most day-to-day forms, stick with v2 checkbox and let the rule do everything.

Testing. Because the rule makes an HTTP call, fake it in feature tests rather than hitting Google:

Http::fake([
    'https://www.google.com/recaptcha/api/siteverify*' => Http::response([
        'success' => true,
    ], 200),
]);

Or use Google's documented public test keys in .env.testing and skip the fake, since Google explicitly accepts them.

Gotchas and Tips

  • Rule name vs. field name. captcha is the rule; g-recaptcha-response is the field. Misreading this is the number-one source of "validation always fails" tickets.
  • Missing .env keys produce silent failures, not exceptions. The package will happily call Google with an empty secret and Google returns {"success": false}. If the rule always fails, check config('captcha.secret') first.
  • Config cache. After editing .env or config/captcha.php, run php artisan config:clear (and config:cache in production). If the site key in the rendered widget doesn't match your dashboard, this is usually why.
  • Local development. On localhost reCAPTCHA v2 works but v3 score thresholds can reject you aggressively because Google can't fingerprint the environment. Use the Google test keys (6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI site / 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe secret) locally — they always pass and never call the real service.
  • Token reuse. reCAPTCHA tokens are single-use and expire in ~2 minutes. If you see "captcha failed" on a slow form, the user sat too long before submitting. Re-render the widget after an AJAX submission or reset with grecaptcha.reset().
  • Network/timeouts. The default HTTP client config uses a 30s timeout and verify => true. Behind a corporate proxy or in a Docker network without CA certs you'll get a RequestException rather than a validation failure — catch it separately or set CAPTCHA_VERIFY=false (via the options array) for local containers, and never in production.
  • Error codes are opaque by default. Google's siteverify response includes error-codes (e.g. invalid-input-secret, timeout-or-duplicate), but the validation rule collapses everything into a single boolean. If you need to distinguish "bad secret" from "user token expired" in logs, do the manual Captcha::verify() call and inspect the response yourself.
  • Facade alias in older Laravel. With auto-discovery, Captcha::render() works out of the box. On Laravel ≤5.4 you must register Buzz\LaravelGoogleCaptcha\CaptchaServiceProvider and alias Buzz\LaravelGoogleCaptcha\Facades\Captcha as Captcha in config/app.php, otherwise you get a Class 'Captcha' not found in Blade.
  • Extension points are thin. The package exposes config for site key, secret, widget attributes, and HTTP options (timeout, headers, verify). It does not expose a pluggable verifier or event hooks — if you need enterprise reCAPTCHA or a custom proxy endpoint, fork the small Captcha class rather than fighting the config. With only ~200 stars and no downstream dependents, expect to own fixes yourself.
  • Don't put the secret in Blade. It's tempting to render both keys with the widget; only CAPTCHA_SITEKEY belongs client-side. If CAPTCHA_SECRET shows up in view source, rotate it in the Google admin console immediately.
  • Testing with the rule mocked. If you mock the Captcha facade (Captcha::shouldReceive('verify')->andReturn(true)), remember the validation rule calls the facade internally — so a facade partial mock is enough to bypass the network in unit tests without Http::fake.
  • Keep the package pinned loosely but audited. Last release was 2025-03-04, and it depends on Guzzle/HTTP client versions that Laravel occasionally bumps. Run composer why-not buzz/laravel-google-captcha after major Laravel upgrades; if a constraint blocks you, the ~5 files in src/ are small enough to vendor.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin