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

No Captcha Laravel Package

arcanedev/no-captcha

Laravel wrapper for Google reCAPTCHA v2 and v3 that simplifies spam protection. Includes validation rules, Blade directives, helpers, and configurable options so you can secure forms and verify tokens with minimal setup and clean configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config

composer require arcanedev/no-captcha
php artisan vendor:publish --provider="Arcanedev\NoCaptcha\NoCaptchaServiceProvider"

The service provider is auto-discovered; the NoCaptcha facade alias is registered for you. The published config/no-captcha.php is the first file to open — it holds secret_key, site_key, widget options, skip_ip, and error_message.

Get keys before touching code

Create a reCAPTCHA v2 site in the Google reCAPTCHA admin console (choose "reCAPTCHA v2 / I'm not a robot" or "Invisible reCAPTCHA badge"). Copy the site key and secret key into .env:

NOCAPTCHA_SITEKEY=your-site-key
NOCAPTCHA_SECRET=your-secret-key

(Confirm the exact env names in the published config — older docs sometimes use RECAPTCHA_*.)

First use case: a contact form

<form method="POST" action="/contact">
    @csrf
    <input name="email" type="email">
    <textarea name="message"></textarea>

    {!! NoCaptcha::renderJs() !!}
    {!! NoCaptcha::display() !!}

    <button type="submit">Send</button>
</form>
$request->validate([
    'email'   => 'required|email',
    'message' => 'required',
    'g-recaptcha-response' => 'required|captcha',
]);

That is the entire happy path: render the widget, submit the token in g-recaptcha-response, and let the package's captcha validation rule verify it against Google.

Where to look first when something is off

  1. config/no-captcha.php — keys, skip_ip, options.
  2. NoCaptcha facade methods (display, renderJs, displaySubmit, verifyResponse, setLang).
  3. The registered captcha validator rule (source: src/NoCaptchaServiceProvider.php / src/Validator.php).

Implementation Patterns

Blade partial for consistent rendering

Wrap the widget so every form uses the same options and only loads the JS once:

{{-- resources/views/partials/captcha.blade.php --}}
@once
    {!! NoCaptcha::renderJs(app()->getLocale()) !!}
@endonce
{!! NoCaptcha::display() !!}

@once prevents duplicate <script src="...recaptcha/api.js"> tags when several forms are on one page.

Invisible reCAPTCHA (bind to form submit)

Use invisible v2 when you don't want a checkbox. Configure options in config, then use displaySubmit:

{!! NoCaptcha::renderJs() !!}
{!! NoCaptcha::displaySubmit('contact-form', 'Send') !!}

displaySubmit($formId, $buttonText) renders the invisible widget plus the callback wiring that submits #contact-form after the challenge succeeds. The form must still include g-recaptcha-response server-side validation.

AJAX / JSON API submissions

When the form is submitted by fetch/axios, verify manually instead of using the rule:

use Arcanedev\NoCaptcha\Facades\NoCaptcha;

$response = NoCaptcha::verifyResponse(
    $request->input('g-recaptcha-response'),
    $request->ip()
);

if (! $response->isSuccess()) {
    return response()->json(['error' => 'Captcha failed'], 422);
}

This is the escape hatch for SPA front-ends (Vue/React/Livewire) where the Blade rule doesn't fit.

Localization of the widget

{!! NoCaptcha::renderJs('fr') !!}
{{-- or globally --}}
@php NoCaptcha::setLang('fr'); @endphp

Google renders the challenge in the page language only if you pass it; otherwise it detects from the browser.

Reusable Form Request

Move the rule out of the controller:

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

Keeps the controller clean and centralizes the captcha requirement.

Testing pattern (avoid hitting Google)

Bind a fake so the captcha rule always passes in tests, or disable the widget for the testing environment:

// tests/TestCase.php or a dedicated provider
$this->app->bind(\Arcanedev\NoCaptcha\Contracts\NoCaptcha::class, function () {
    return new class implements \Arcanedev\NoCaptcha\Contracts\NoCaptcha {
        public function verifyResponse($response, $clientIp = null) {
            return new class {
                public function isSuccess() { return true; }
            };
        }
        // ...other interface stubs
    };
});

Simpler alternative: add the test runner's IP (127.0.0.1) to skip_ip in config/no-captcha.php only under APP_ENV=testing, or conditionally disable validation in the Form Request.

Multiple widgets on one page

If you have more than one form, each display() call generates its own g-recaptcha div. Ensure you only call renderJs() once (@once), and give each widget a unique callback if you're using invisible mode — Google's JS binds by data attributes, so duplicate callbacks will collide.


Gotchas and Tips

reCAPTCHA v3 is not supported. This package wraps the v2 API (checkbox and invisible). There is no built-in score/action handling, no execute() helper. If you need v3 you'll call Google's siteverify endpoint yourself (or use a dedicated v3 package) — NoCaptcha::verifyResponse() will still verify the token, but you must interpret the returned score yourself. For new projects, weigh v2 vs v3 before adopting this package.

Package age and Laravel version constraints. Last release was Feb 2022. Check the composer.json constraint against your Laravel version; you may need to bump constraints locally or fork. Auto-discovery and the facade generally keep working across Laravel versions, but don't assume Laravel 11 is officially supported.

Tokens are single-use and expire (~2 minutes). Never cache or reuse g-recaptcha-response. If you verify manually, verify at the start of the request; on failure, re-render the widget. A stale token produces a confusing validation error on a slow form.

hideBadge has legal strings attached. If you set hideBadge => true, Google's terms require you to display the reCAPTCHA attribution text ("This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply") somewhere visible. The package won't do it for you.

Empty or placeholder keys fail silently / confusingly. If NOCAPTCHA_SECRET is blank, verifyResponse returns a failure rather than throwing a friendly exception. Add a boot-time check in a service provider or a config sanity assertion in non-production environments.

Config caching. After changing .env or config/no-captcha.php, run php artisan config:clear. In production, config:cache is fine but you must redeploy (or clear) when keys rotate.

Domain registration. Google validates the host it's embedded on. If you test on localhost but deploy to staging.example.com, that host must be added in the reCAPTCHA console, or you get a "invalid domain for site key" error in the browser console — not a server-side validation error, so check the browser first.

Rule name collisions. The rule is registered as captcha. Another package using the same rule name will overwrite it; the symptom is "captcha validation always passes" or "class not found" in the validator. Inspect Validator::extend calls if behavior seems wrong.

Local development. Don't disable captcha by commenting out the rule — use skip_ip in config (e.g. ['127.0.0.1', '::1']) or conditionally strip the rule in a Form Request when app()->environment('local'). This keeps staging/production honest.

Script duplication. Calling NoCaptcha::renderJs() inside a form partial included by multiple forms injects the Google script more than once. Use @once or move renderJs() to your layout <head>.

Error message customization. Default error text lives in config('no-captcha.error_message') (with a :attribute placeholder). Override there rather than in every Form Request.

Manual verification for APIs returns a response object. verifyResponse() returns a wrapper — check isSuccess() and, on failure, log the underlying getErrorCodes() so you can distinguish "invalid keys" (a config/deployment bug) from "user failed the challenge" (a user problem). That distinction saves hours during launches.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels