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 Captcha Laravel Package

rahul900day/laravel-captcha

rahul900day/laravel-captcha adds easy CAPTCHA protection to Laravel forms, supporting reCAPTCHA, hCaptcha, and custom drivers through a clean API. It includes validation rules, Blade rendering, and configurable options so you can secure any form in minutes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config

composer require rahul900day/laravel-captcha:^5.0
php artisan vendor:publish --tag=captcha-config

Version 5.0.0 is the Laravel 13 compatibility release — the only change in the notes is "Laravel 13 Support." Because it shipped as a major bump, assume the illuminate/* constraints in composer.json were widened upward and older framework versions dropped. Check that constraint before you commit: if you're still on Laravel 12 or earlier, stay on the 4.x line, and don't run a bare composer update on a project that mixes framework versions.

Laravel's package auto-discovery registers the service provider and the Captcha facade (Rahul900day\Captcha\Facades\Captcha). This is still a low-adoption package (0 dependents, ~108 stars), so version compatibility remains your first real risk — the 5.0.0 release proves it tracks Laravel majors, but it also proves a major of the framework forces a major of the package.

Set the driver in .env

CAPTCHA_DRIVER=math

# only needed for the remote drivers
RECAPTCHA_SITE_KEY=
RECAPTCHA_SECRET_KEY=
HCAPTCHA_SITE_KEY=
HCAPTCHA_SECRET_KEY=

If you change these after a deploy, run php artisan config:cache again or the old values stay baked in.

Where to look first

  1. config/captcha.php — the default driver and the per-driver blocks. This tells you which drivers actually ship in your installed version.
  2. src/Drivers/ — each driver is a small class; read the one you plan to use to see exactly what create() returns and what validate() expects.
  3. The Captcha facade docblock — the authoritative method signatures.

First use case: a math captcha on a contact form

// ContactController@show
public function show()
{
    return view('contact', [
        'captcha' => Captcha::create('math'), // returns key + renderable payload
    ]);
}
{{-- contact.blade.php --}}
<img src="{{ $captcha['image'] }}" alt="Solve the challenge">
<input type="hidden" name="captcha_key" value="{{ $captcha['key'] }}">
<input type="text" name="captcha" required>
// ContactController@store
public function store(Request $request)
{
    $request->validate(['captcha' => ['required', 'string']]);

    if (! Captcha::validate($request->input('captcha_key'), $request->input('captcha'))) {
        return back()
            ->withErrors(['captcha' => 'That answer was incorrect.'])
            ->withInput();
    }
}

That create → render → validate loop is 90% of real-world usage. Everything below is variations on it.

Implementation Patterns

Generate in the controller, not in Blade. Rendering Captcha::create() inside a view means it re-runs on every render of that view (including the back() redirect after a failed submit), producing a new key while the user's session still references the old one. Generate once per GET, pass the payload down.

Wrap the form half in a Blade component. It keeps the hidden-key/input pairing consistent and gives you one place to change driver-specific markup:

{{-- resources/views/components/captcha.blade.php --}}
@props(['captcha'])
<div {{ $attributes }}>
    <img src="{{ $captcha['image'] }}" alt="CAPTCHA">
    <input type="hidden" name="captcha_key" value="{{ $captcha['key'] }}">
    <input type="text" name="captcha" required autocomplete="off">
</div>

Move validation into a FormRequest. The two-argument validate(key, value) call doesn't map cleanly onto a rule string, so do the check in withValidator() or after() so both your normal rules and the captcha failure surface in the same $errors bag.

Remote drivers (reCAPTCHA / hCaptcha) are a different shape. There's no server-generated key — you render the vendor widget with the site key, the browser posts a token, and you validate that token server-side using the secret key. Two consequences:

  • The token is single-use and expires (typically ~2 minutes). Don't validate it in middleware and again in the controller.
  • Always check hostname and (for v3) action/score in the response, not just success. A token minted on someone else's site will pass success and fail everything else. If the driver doesn't expose those fields, call the vendor endpoint yourself.

Inertia / Livewire / SPA front ends. Expose a tiny JSON endpoint:

Route::get('/captcha/{driver?}', fn (?string $driver = null) => response()->json(
    Captcha::create($driver ?? config('captcha.default'))
))->middleware('throttle:30,1');

The client fetches a fresh challenge on mount and again on every validation failure. This is where the package earns its keep over hand-rolling — one endpoint covers math, image, and widget drivers behind the same contract.

Always pair it with rate limiting. Math and short-answer captchas have a tiny answer space. throttle:5,1 on the submit route is not optional; without it the captcha is decorative.

Testing. Feature tests will fail on captcha for the boring reason that the key store is array cache, which resets between requests. Two workable approaches:

  • Swap the driver to something inert in phpunit.xml / phpunit.xml.dist env, so real requests skip the check.
  • Mock the facade in the test that cares about the happy path: Captcha::shouldReceive('validate')->once()->andReturn(true); and keep one dedicated test asserting the failure path against the real driver.

Swap-proof your code. The package is small and lightly adopted; the blast radius of it being abandoned is your whole auth/contact surface. Put a CaptchaService interface in front of it with generate() and verify() methods, and resolve the package facade inside that one class. When you need to move to a maintained alternative — or when the next Laravel major lands and 5.x hasn't caught up yet — it's a one-file change.

Gotchas and Tips

Upgrading to 5.0.0 is a framework-compat bump, not a feature bump. Nothing in the notes suggests API changes — just Laravel 13 support — but the major version means the old illuminate/* constraints are gone. Treat the upgrade as: bump the constraint, run the full suite against your captcha endpoints, and spot-check any custom driver you wrote, since the driver contract is the most likely thing to have moved. If you publish views or lang files, re-publish or diff them after upgrading in case the vendor paths shifted.

The key has a TTL and is single-use. If the user leaves the tab open for ten minutes, validation fails with a "wrong answer" message that looks like a user error but is really an expiry. Give the error copy room ("This challenge expired — please try again") and always regenerate on failure. Never re-render the same key back into the form.

Cache/session configuration matters. Depending on the version, generated keys live in the cache or session store. CACHE_DRIVER=array (Laravel's test and some local defaults) silently discards them between requests, which produces "captcha always fails locally but works in staging." Check config/cache.php before you file a bug against the package.

Image driver needs GD and a font. extension_loaded('gd') returning false is the most common cause of a broken image. The payload is a base64 data URI, so there's no storage disk involved — but it does mean a fat HTML response per form; don't put three image captchas on one page.

Config cache is a trap during rollout. CAPTCHA_DRIVER changes without php artisan config:cache will appear to do nothing. Same for secret keys — a stale cached config with an empty secret produces vendor API errors that read like network failures. This bites hardest right after a major upgrade, when you're changing both the package version and the config in the same deploy.

Remote driver debugging. Log the raw vendor response before you interpret it. A success: false with error-codes: ["invalid-input-response"] means expired/duplicate token; ["timeout-or-duplicate"] means you validated twice. Almost every "reCAPTCHA is broken" report is one of those two, or a domain that isn't whitelisted for the site key in the vendor console.

Never log or expose the secret key. The site key is public by design; the secret key is not. Keep both in .env, never in config/captcha.php literals, or config:cache will ship them into a file that gets committed by accident.

Accessibility is on you. Image and math captchas have no audio fallback in a package this size. If your form must be WCAG-compliant, plan for either the remote drivers (which ship audio challenges) or a honeypot-plus-timing approach for low-risk forms.

Extension points worth knowing. A custom driver is usually a small class implementing the package's driver contract plus a config block — cheaper than forking. Language strings and views (where published) live under resources/lang/vendor/ and resources/views/vendor/, so error copy can be localized without touching the source.

Treat the dependency as fragile. MIT license, no dependents, and a single maintainer. The 5.0.0 release is reassuring — the package is alive and tracks Laravel majors — but it also means your upgrade path is coupled to the maintainer's free time, and there's no backport promise for the version you're on. Pin the exact or caret version in composer.json, watch the repo whenever a new Laravel major ships, and keep the abstraction layer described above. The package is a reasonable way to get three captcha types behind one API quickly; it is not something to build a compliance story on without a fallback.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core