danharrin/livewire-rate-limiting
Add rate limiting to Laravel Livewire actions to stop spam and brute-force clicks. Configure limits per component or method, with customizable keys, decay times, and responses when limits are exceeded—simple protection that feels native to Livewire.
Installation
composer require danharrin/livewire-rate-limiting
Publish the config file (if needed):
php artisan vendor:publish --provider="DanHarrin\LivewireRateLimiting\LivewireRateLimitingServiceProvider"
Basic Setup
rateLimited() method or uses the RateLimited trait.config/livewire-rate-limiting.php (e.g., 5/minute for a form submission).First Use Case Apply rate limiting to a Livewire action:
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class MyComponent extends Component
{
use WithRateLimiting;
public function submitForm()
{
$this->rateLimited('submitForm', 5); // 5 requests per minute
// Rest of the logic...
}
}
Per-Action Rate Limiting
Use rateLimited() on specific actions to enforce throttling:
public function deleteItem()
{
$this->rateLimited('deleteItem', 3); // 3 deletes per minute
// Delete logic...
}
Global Rate Limiting Apply a default limit to all actions in the component:
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class MyComponent extends Component
{
use WithRateLimiting;
protected $rateLimits = [
'*' => 10, // 10 requests per minute for all actions
];
}
Dynamic Limits Fetch limits from a database or API:
public function submitForm()
{
$limit = UserRateLimit::forUser(auth()->id())->getLimit();
$this->rateLimited('submitForm', $limit);
}
Custom Storage
Override the default storage (e.g., Redis) by binding a custom RateLimitStore:
$this->app->bind(\DanHarrin\LivewireRateLimiting\RateLimitStore::class, function () {
return new \DanHarrin\LivewireRateLimiting\Stores\RedisStore();
});
User-Specific Limits Scope limits by user or IP:
$this->rateLimited('submitForm', 5, 'user:' . auth()->id());
wire:click or wire:submit to trigger actions.wire:message or custom JS to show rate-limit notifications:
@if ($this->exceededRateLimit('submitForm'))
<div class="alert">Too many requests. Try again later.</div>
@endif
if ($this->exceededRateLimit('submitForm')) {
\Log::warning('Rate limit exceeded for submitForm', ['user' => auth()->id()]);
}
Cache Invalidation
default_ttl in config/livewire-rate-limiting.php (e.g., 60 seconds).Key Collisions
user:123) must be unique. Reusing keys for unrelated actions can cause false limits.user:123:submitForm.Livewire Server-Side Rendering
$this->rateLimited('submitForm', 5, 'ip:' . request()->ip());
Testing Quirks
RateLimitStore in tests to avoid hitting real limits:
$this->app->instance(RateLimitStore::class, new MockStore());
Concurrent Requests
sync on the component or adjust limits for high-traffic actions.Check Limits Inspect the store directly:
$store = app(RateLimitStore::class);
$remaining = $store->remaining('user:123:submitForm');
Enable Logging
Add debug logs in config/livewire-rate-limiting.php:
'debug' => env('RATE_LIMIT_DEBUG', false),
Common Errors
Call to undefined method: Ensure the WithRateLimiting trait is used.RateLimitStore not bound: Register the store in AppServiceProvider:
$this->app->bind(RateLimitStore::class, function () {
return new \DanHarrin\LivewireRateLimiting\Stores\CacheStore();
});
Custom Stores
Implement DanHarrin\LivewireRateLimiting\Contracts\RateLimitStore for databases or external APIs:
class DatabaseStore implements RateLimitStore {
public function remaining(string $key): int { ... }
public function increment(string $key): void { ... }
}
Middleware Apply rate limiting globally via middleware:
public function handle(Request $request, Closure $next) {
if ($request->wireable) {
$this->app->make(WithRateLimiting::class)->checkRateLimits();
}
return $next($request);
}
Event Listeners Trigger events on rate limit exceeded:
event(new RateLimitExceeded($key, $remaining));
UI Customization
Extend the default RateLimited view or use Alpine.js for dynamic counters:
<span x-text="remainingAttempts"></span>
<script>
document.addEventListener('livewire:init', () => {
Livewire.on('rateLimitUpdated', ({ remaining }) => {
this.remainingAttempts = remaining;
});
});
</script>
How can I help you explore Laravel packages today?