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

zaber-dev/laravel-cooldown

Laravel Cooldown manages persistent, entity-scoped action cooldowns for Laravel apps. Enforce limits on workflows, routes, and Eloquent models using cache or database storage, with atomic execution, middleware, and custom backends.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and scaffold its config (and database migration if you plan to use the persistent driver):

composer require zaber-dev/laravel-cooldown
php artisan vendor:publish --tag=cooldown-config
php artisan vendor:publish --tag=cooldown-migrations   # only for the database store
php artisan migrate

Where to look first: config/cooldown.php. That file is the whole mental model of the package — it tells you which store is active (cache vs. database), which cache store/connection backs it, the default cooldown duration, and the key prefix used to namespace entries. Read it before writing any cooldown logic, because most surprises in this package are configuration, not code.

First use case: a "resend verification email" button. The fluent API is the day-to-day surface:

use ZaberDev\Cooldown\Facades\Cooldown;

// Start a cooldown for this user's resend action
if (! Cooldown::for("verify-email:{$user->id}")->seconds(60)->start()) {
    return back()->with('error', 'Please wait before requesting another email.');
}

Mail::to($user)->send(new VerifyEmail($user));

The key insight to internalize early: the cooldown is keyed by your string, not by the action class. verify-email:42 and verify-email:43 are independent, and verify-email:42 from two different controllers is the same cooldown. Namespacing is your responsibility.

Implementation Patterns

Per-actor keys, built from a helper. Don't scatter string interpolation across controllers. Wrap it:

protected function cooldownKey(string $action, ?User $user = null): string
{
    return sprintf('%s:%s:%s', $action, $user?->id ?? request()->ip(), tenant()->id);
}

This keeps the tenant/user scoping consistent and makes it obvious in one place that cooldowns are namespaced per tenant — a requirement that surfaces the moment you add a second customer.

Eloquent integration for model-bound actions. The package ships a trait for attaching cooldowns to models, which is the right pattern when the cooldown is a property of a record (a report you can only regenerate hourly, a post you can only bump once a day):

class Report extends Model
{
    use HasCooldowns;
}

if ($report->isOnCooldown('regenerate')) {
    return response()->json(['retry_after' => $report->cooldownRemaining('regenerate')], 429);
}

$report->startCooldown('regenerate', minutes: 60);

This is better than a manual cache key because the cooldown lifecycle follows the model — delete the report and the cooldown is scoped to an entity that no longer exists, so pair it with the database store if you need that persistence.

Middleware for declarative route protection. For endpoints where the cooldown is a policy rather than a business rule (contact forms, webhook-triggering routes, unauthenticated POSTs), push it into the route definition:

Route::post('/contact', ContactController::class)
    ->middleware('cooldown:contact-form,30');

This mirrors Laravel's own throttle middleware and is the cheapest thing to review in a diff. Use it for blanket protection; drop to the fluent API when the cooldown needs to be conditional on business state.

Choose the store by deployment shape, not by preference. Cache store for anything ephemeral and high-frequency (button spam, AJAX endpoints) — Redis or Memcached gives you a shared, fast, TTL-native hit. Database store when you need durability across cache flushes, an audit trail, or when the cooldown is meaningful to users ("you can do this again tomorrow") and losing it on a deploy would be a bug. Mixing is fine: the store choice is config, and a custom driver can be swapped in per-call if the package exposes it — which it does via its storage contract.

Test by traveling time, not by sleeping. Cache TTLs are wall-clock; your test suite should be too:

protected function setUp(): void
{
    parent::setUp();
    Cooldown::clear('contact-form:127.0.0.1'); // or flush the whole store in tests
}

public function test_contact_form_is_cooled_down(): void
{
    $this->post('/contact')->assertRedirect();

    $this->post('/contact')->assertStatus(429);

    $this->travel(31)->seconds();
    $this->post('/contact')->assertRedirect();
}

If the package provides a fake or a null driver, use it — otherwise point the config at the array cache store in phpunit.xml so tests never touch Redis.

Gotchas and Tips

Check-then-act is not atomic. isOnCooldown() followed by start() is a race: two concurrent requests can both read "clear" and both proceed. Whenever the action is expensive or irreversible, drive the decision off the result of the write — start() returning false is your gate — rather than a preceding read. On Redis this is a single SET NX; on the database store it depends on how the package writes and isn't free of interleaving. If the operation truly must be once-only, back it with a queued job and a unique constraint, and treat the cooldown as UX sugar.

The two stores fail in opposite directions. The cache store silently expires on eviction or a cache:clear, and cooldowns vanish (usually harmless, occasionally a policy hole). The database store survives deploys but the table grows unboundedly — expired rows are not automatically garbage unless the package schedules pruning. Check for a prune command and put it on the scheduler, otherwise a busy endpoint quietly accumulates millions of dead rows.

Cache tags don't work on file/database cache drivers. If you plan to bulk-clear by tag, you need Redis or Memcached. Otherwise namespace your keys carefully and clear individually, or you'll be flushing the entire application cache to reset one cooldown.

Multi-node deployments need a shared store. Defaulting to the file cache driver means each app server has its own cooldown state and a user can bypass the limit by round-robining across nodes. Point the package at Redis explicitly rather than inheriting the app default.

Middleware responses need shaping. A raw 429 with no body is a poor API contract. Either render it yourself in the exception handler or configure the package's response if it exposes one — and make sure Retry-After is populated, since the whole point of a cooldown is telling the client when to come back.

Debugging is a key-lookup problem. When a cooldown "doesn't work," dump the resolved key and inspect that exact entry in your cache or table (php artisan cache:get, or a direct select on the cooldowns table). Nine times out of ten the bug is a key mismatch — a user ID vs. a session ID, a trailing colon, an email that got normalized in one code path and not another.

Verify names before you commit. This package is young and thin on dependents; the README is the reference, but method signatures (for(), seconds(), start(), isOnCooldown(), cooldownRemaining()), the trait name, and the migration tag above should be confirmed against vendor/zaber-dev/laravel-cooldown/ on install. The concepts are stable; the exact spellings are the part that will cost you ten minutes.

Extension point worth knowing: the storage contract. If neither cache nor database fits — say you need cooldowns shared with a non-Laravel service — implement the contract over your own backing store and register it in config. That is a small class and saves you from reimplementing the fluent API.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style