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

Recaptcha Laravel Package

fruitcakestudio/recaptcha

Laravel integration for Google reCAPTCHA. Adds validation rules, Blade directives and middleware to protect forms and routes from bots and spam, with support for reCAPTCHA v2 and v3 plus configurable keys, score thresholds and error messages.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and inspect before trusting the README.

composer require fruitcakestudio/recaptcha

The first thing to check is composer.json in vendor/fruitcakestudio/recaptcha: there is no extra.laravel block, so there is no auto-discovery, no service provider, no facade, and no config/recaptcha.php to publish. This is a plain PHP wrapper around Google's https://www.google.com/recaptcha/api/siteverify endpoint, not a Laravel package. You wire it up yourself.

Read in this order:

  1. vendor/fruitcakestudio/recaptcha/src/ — there is essentially one class plus maybe an exception type. That is the entire API surface.
  2. composer.json for the PHP constraint. This was last released in July 2017, so expect php: ^5.6 || ^7.0 and a pinned Guzzle range. On PHP 8.x you will likely need --ignore-platform-reqs or a fork.
  3. The class constructor signature to confirm how the secret key is passed (constructor arg vs. setter) and what the verify method returns (bool vs. throwing on failure).

First use case — a contact or login form:

Render the widget with your site key in Blade (the package has nothing to do with the client side):

<form method="POST" action="/contact">
    @csrf
    <div class="g-recaptcha" data-sitekey="{{ config('services.recaptcha.site_key') }}"></div>
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    <button>Send</button>
</form>

Then verify the token server-side, in the controller or better a FormRequest:

$captcha = new \Fruitcake\Recaptcha\Recaptcha(config('services.recaptcha.secret_key'));

if (! $captcha->verify($request->input('g-recaptcha-response'))) {
    return back()->withErrors(['g-recaptcha-response' => 'Captcha failed.']);
}

Confirm the exact method name against src/ — the package predates any convention, and the wrapper may throw an exception on network failure rather than returning false.

Implementation Patterns

Bind it as a singleton in a service provider. Since the package ships no provider, this is your job and it keeps the secret out of your controllers:

// app/Providers/AppServiceProvider.php
$this->app->singleton(\Fruitcake\Recaptcha\Recaptcha::class, function () {
    return new \Fruitcake\Recaptcha\Recaptcha(
        config('services.recaptcha.secret_key')
    );
});

Put the keys in config/services.php alongside your other third-party credentials, not in a package config file that does not exist.

Wrap it in a validation rule. This is the highest-leverage pattern because it makes the captcha a first-class citizen of Laravel's validation pipeline and gives you one place to bypass in tests:

Validator::extend('recaptcha', function ($attribute, $value, $parameters, $validator) {
    if (app()->environment('testing', 'local')) {
        return true;
    }

    return app(\Fruitcake\Recaptcha\Recaptcha::class)->verify((string) $value);
}, 'The captcha verification failed.');

Now any form gets 'g-recaptcha-response' => 'required|recaptcha' and the error message flows through $errors like everything else.

Middleware for global protection. For a public endpoint being hammered, verify in middleware rather than per-controller:

public function handle($request, Closure $next)
{
    if (! app(\Fruitcake\Recaptcha\Recaptcha::class)
            ->verify($request->input('g-recaptcha-response'))) {
        abort(422, 'Captcha required.');
    }

    return $next($request);
}

Register it after VerifyCsrfToken and only on the specific routes — a global reCAPTCHA on every POST will break your own AJAX and webhook endpoints.

Adapter contract for swappability. Given the package's age, do not let Fruitcake\Recaptcha\Recaptcha leak into ten controllers. Define a one-method interface (CaptchaContract::verify(string $token): bool) and bind the package behind it. When you migrate to google/recaptcha or biscolab/laravel-recaptcha, you change one binding.

Multiple key sets. The package instance holds one secret. If you use an invisible reCAPTCHA on one form and a checkbox on another with different key pairs, instantiate per-form rather than fighting the singleton — or bind two named singletons (recaptcha.contact, recaptcha.checkout).

Gotchas and Tips

It is a 2017 library targeting v2. There is no first-class support for reCAPTCHA v3 scores, action verification, or hostname checking. If you need v3, the response JSON includes score and action, but you would be reaching into raw response data the package likely discards. The realistic answer for anything new is the official google/recaptcha package or biscolab/laravel-recaptcha; for a legacy app already on this one, wrap it and move on.

Tokens are single-use and expire in ~2 minutes. The single most common bug is validating twice — e.g. once in middleware and again in a FormRequest. The second call returns timeout-or-duplicate. Pick exactly one validation point.

Treat network failure as its own case. If Google is unreachable or TLS fails, a naive verify() returns false and you've silently locked out every user while your logs say "captcha invalid." Distinguish "Google said no" from "we couldn't ask Google," and decide explicitly whether to fail open or closed. Also set a sane cURL/Guzzle timeout — the default can stall your request for seconds.

Check for SSL/CA bundle issues on older stacks. A 2017 Guzzle pin plus an outdated cacert.pem produces opaque verification failures. Test the same call with a plain curl first to isolate the package from the environment.

Error codes are the only debugging signal. Log the error-codes array from the response: missing-input-secret and invalid-input-secret mean config, invalid-input-response means the widget never loaded or the field name is wrong, timeout-or-duplicate means double validation. Without logging these you will be guessing.

Domain registration. localhost and 127.0.0.1 need to be explicitly registered in the reCAPTCHA admin console for local development, and staging domains need their own key pair (or must be added to the existing one). Otherwise the widget silently refuses the token and you see invalid-input-response with no obvious cause.

Never run real verification in tests. Bind a fake implementation, or short-circuit on app()->environment('testing') as above. A test suite that hits Google is slow, flaky, and will get rate-limited.

The field name is fixed. Google's widget posts g-recaptcha-response; if you rename the field in your form the server-side check fails with no hint. If you need a custom name, map it before calling verify().

Watch the g-recaptcha-response on AJAX/JSON endpoints. The widget only injects the hidden field on form submit. For SPA-style calls you must call grecaptcha.getResponse() and send the token yourself — a frequent source of "works on the contact form, not on the API" bug reports.

No maintenance means no security patches. Set a reminder to re-evaluate this dependency; the exit cost is tiny if you kept the adapter contract, and painful if Fruitcake\Recaptcha\Recaptcha is referenced in twenty places.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package