Ultra-easy CAPTCHA integration for Laravel 9, 10, 11, and 12.
justchill/laravel-captcha provides a minimal, flexible CAPTCHA system with math, word, and image rendering, session-based validation, Blade directive support, and optional middleware integration.
- PHP ^8.0
- Laravel ^9.0|^10.0|^11.0|^12.0
- GD Extension (optional, for image CAPTCHA)
- β Laravel 9β12 support
- π§ Three CAPTCHA types: Math, Word, and Image
- π Session-based validation with expiration
- π§© Simple Blade directive:
@captcha - π Middleware enforcement
- π Built-in validation rule:
captcha - πΌοΈ Image-based CAPTCHA with GD support
- βοΈ Configurable attempts limit and expiration
- π οΈ Publishable config, views, and language files
- βοΈ Case-sensitive validation (configurable)
composer require justchill/laravel-captchaAdd to your composer.json:
{
"repositories": [
{
"type": "path",
"url": "../laravel-captcha",
"options": {
"symlink": true
}
}
],
"require": {
"justchill/laravel-captcha": "*"
},
"minimum-stability": "dev",
"prefer-stable": true
}Then run:
composer require justchill/laravel-captchaUbuntu/Debian:
sudo apt-get install php-gd
sudo service apache2 restart # or php-fpmCentOS/RHEL:
sudo yum install php-gd
sudo systemctl restart httpdVerify installation:
php -m | grep gdPublish configuration:
php artisan vendor:publish --tag=captcha-configThis creates:
config/captcha.php- Main configuration
To publish views (optional, for customization):
php artisan vendor:publish --tag=captcha-viewsThis creates:
resources/views/vendor/captcha/challenge.blade.php- Customizable viewresources/views/vendor/captcha/_challenge-body.blade.php- Challenge body partial
To publish language files (optional):
php artisan vendor:publish --tag=captcha-langThis creates language files in lang/vendor/captcha/ for translations.
return [
'type' => env('CAPTCHA_TYPE', 'math'), // Options: math, word, image
'expires_minutes' => env('CAPTCHA_EXPIRES_MINUTES', 10),
'max_attempts' => env('CAPTCHA_MAX_ATTEMPTS', 5),
'case_sensitive' => env('CAPTCHA_CASE_SENSITIVE', true),
'allowed_chars' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789',
'length' => [
'word' => 6,
'image' => 5,
],
'fonts' => [
base_path('vendor/justchill/laravel-captcha/src/fonts/Roboto-Bold.ttf'),
],
'image' => [
'width' => 150,
'height' => 50,
'font_size' => 24,
'bg_color' => '#ffffff',
'text_color' => '#000000',
'noise' => true,
'lines' => 3,
],
'math_difficulty' => 'easy',
];Simply add the @captcha directive to your form:
<form method="POST" action="/submit">
@csrf
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
@captcha
<button type="submit">Submit</button>
</form>@include('captcha::challenge'){!! app('captcha')->render('image') !!}public function store(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required|min:8',
'captcha' => 'required|captcha',
]);
// Process form...
}Protect routes with CAPTCHA middleware:
Route::post('/register', [RegisterController::class, 'store'])
->middleware('captcha');
Route::group(['middleware' => 'captcha'], function () {
Route::post('/contact', [ContactController::class, 'send']);
Route::post('/comment', [CommentController::class, 'store']);
});use JustChill\LaravelCaptcha\Facades\Captcha;
if (Captcha::validate($request->input('captcha'))) {
// CAPTCHA is valid
} else {
// CAPTCHA is invalid
}Simple arithmetic questions:
CAPTCHA_TYPE=mathExample: "What is 15 + 7?"
Random character strings:
CAPTCHA_TYPE=wordExample: "Type the word: aBc3Ef"
Visual text rendering (requires GD extension):
CAPTCHA_TYPE=image// In your controller
public function showForm()
{
$challenge = app('captcha')->generate('image');
return view('form', compact('challenge'));
}<!-- In your view -->
@include('captcha::challenge', ['challenge' => $challenge])Edit config/captcha.php:
'math_difficulty' => 'easy', // or 'medium', 'hard'Configure in config/captcha.php or .env:
// In config/captcha.php
'case_sensitive' => false, // or use env('CAPTCHA_CASE_SENSITIVE', false)Or in your .env file:
CAPTCHA_CASE_SENSITIVE=falsePublish views and edit resources/views/vendor/captcha/challenge.blade.php:
php artisan vendor:publish --tag=captcha-viewsThen edit the published view file to customize the styling.
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
<style>
form { max-width: 500px; margin: 50px auto; }
input, textarea { width: 100%; padding: 10px; margin-bottom: 15px; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; }
.error { color: red; font-size: 14px; }
</style>
</head>
<body>
<form method="POST" action="{{ route('contact.send') }}">
@csrf
<div>
<label>Name</label>
<input type="text" name="name" value="{{ old('name') }}" required>
@error('name') <span class="error">{{ $message }}</span> @enderror
</div>
<div>
<label>Email</label>
<input type="email" name="email" value="{{ old('email') }}" required>
@error('email') <span class="error">{{ $message }}</span> @enderror
</div>
<div>
<label>Message</label>
<textarea name="message" rows="5" required>{{ old('message') }}</textarea>
@error('message') <span class="error">{{ $message }}</span> @enderror
</div>
@captcha
<button type="submit">Send Message</button>
</form>
</body>
</html>Controller:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function send(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
'message' => 'required|string',
'captcha' => 'required|captcha',
]);
// Send email or process form...
return back()->with('success', 'Message sent successfully!');
}
}Problem: Blank image or error when using image CAPTCHA.
Solution: Install GD extension:
# Check if GD is installed
php -m | grep gd
# Install GD (Ubuntu/Debian)
sudo apt-get install php8.2-gd # Replace 8.2 with your PHP version
# Restart web server
sudo service apache2 restartProblem: CAPTCHA validation fails even with correct answer.
Solution: Check your session configuration in .env:
SESSION_DRIVER=file # or database, redis
SESSION_LIFETIME=120Clear config cache:
php artisan config:clear
php artisan cache:clearProblem: Validation fails after 5 attempts.
Solution: Adjust max_attempts in config/captcha.php or wait for session to expire (default: 10 minutes).
Problem: Session expires before user submits form.
Solution: Increase expires_minutes in config/captcha.php:
'expires_minutes' => 15, // Changed from 10Run the test suite:
composer testOr with coverage:
composer test-coverage- PHPUnit test coverage
- Audio CAPTCHA (accessibility)
- Refresh button for image CAPTCHAs
- Custom difficulty levels for all types
- Multi-language support (translations included)
- Redis/database storage option
- Rate limiting per IP
Pull requests are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please follow Laravel coding conventions and include test coverage where possible.
- π Report bugs: GitHub Issues
- π¬ Questions: GitHub Discussions
- π§ Email: help@justchill.ng
This project is licensed under the MIT License.
Created with β€οΈ by abram.CataLYST for JustChill Webcreative
If you find this package helpful, please give it a star on GitHub!
Made with β₯ in Nigeria