
Most Laravel applications eventually need some form of cooldown.
Not request throttling—but actual action cooldowns.
Examples include:
- Password reset requests
- Email verification
- OTP / SMS sending
- AI prompt generation
- Report exports
- Payment retries
- Reward claiming
- Expensive background jobs
Laravel already provides an excellent RateLimiter, but it's primarily designed for request throttling (for example, "60 requests per minute").
When I was building an application, I found myself needing something slightly different:
- Persist cooldowns in either cache or database
- Attach cooldowns directly to Eloquent models
- Reuse the same API across controllers, jobs, commands, and middleware
- Store cooldowns for arbitrary targets such as Users, API tokens, IP addresses, or custom identifiers
- Allow switching storage drivers without changing application code
That led me to build Laravel Cooldown.
The Goal
I wanted an API that was expressive enough to read almost like English.
Cooldown::for('password_reset', $user)->enforce();
// Perform the action...
Cooldown::for('password_reset', $user)->for(300);
Checking a cooldown is equally straightforward:
if (Cooldown::for('password_reset', $user)->active()) {
$remaining = Cooldown::for('password_reset', $user)
->info()
->remainingSeconds();
}
Some Design Decisions
Rather than coupling everything to a single storage implementation, the package uses Laravel's Illuminate\Support\Manager to provide interchangeable drivers.
Currently it supports:
- Cache
- Database
Additional drivers can be registered using Laravel's familiar extend() pattern.
Another design decision was to support Eloquent models as first-class cooldown targets.
Instead of manually generating cache keys, you can simply write:
$user->cooldown('email_verification')->for(300);
The package automatically resolves unique keys for models, scalars, IP addresses, and other supported targets.
Middleware That Only Starts on Success
One small behavior that bothered me with many cooldown implementations is that they start counting down before the request has actually succeeded.
Imagine submitting a form that fails validation.
You shouldn't have to wait another minute just because you mistyped your email address.
The middleware in Laravel Cooldown only creates the cooldown after successful (2xx) or redirected (3xx) responses.
Features
- Cache and Database drivers
- Expressive fluent API
- Native Eloquent integration
- Route middleware
- Immutable DTOs
- Event dispatching
- Automatic database pruning
- Custom driver support
Feedback Welcome
This is an open-source project, and I'd really appreciate feedback from the Laravel community.
If you have suggestions for the API, architecture, or documentation—or ideas for additional drivers or features—I'd love to hear them.
Top comments (1)
Update (v1.1.0): Based on some great community feedback, Laravel Cooldown now includes native atomic concurrency protection.
The package now uses an atomic check → lock → execute → set flow to prevent duplicate execution during concurrent requests while still preserving its original success-only cooldown behavior. In other words, cooldowns are only created after successful execution (2xx/3xx), but simultaneous requests for the same action are safely blocked with a temporary in-flight lock.
This release also introduces a new high-level block() API:
This makes workflows such as OTP delivery, payment processing, report generation, and other concurrency-sensitive operations safe out of the box, without requiring additional locking boilerplate.