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 Rate Limiter Laravel Package

aporat/laravel-rate-limiter

Add flexible rate limiting to Laravel routes and APIs with aporat/laravel-rate-limiter. Configure request limits, time windows, and custom keys, then protect endpoints via middleware using Laravel’s cache-backed storage. Ideal for throttling login attempts, webhooks, and public APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Redis-backed, multi-window rate limiter for Laravel. v6 is a breaking release, so start with the README's "Upgrading from v5" section before anything else — the removal list (withRateLimitHeaders(), setRequestTag(), getRequestTag(), checkIpAddress(), the redis.* settings, Lumen support) and the header rename will break existing call sites.

Minimum setup:

  1. Confirm PHP 8.3+. Redis is required at runtime but ext-redis is now optional — use phpredis or predis, whichever your app already ships.
  2. Publish the config and decide on a Redis connection. The package now honors a named connection from config/database.php instead of overriding host/port/database, so point it at an existing connection rather than duplicating credentials.
  3. Review the whitelist. The default is loopback only; 10.0.0.0/8 was deliberately removed. If you sit behind a load balancer, configure trusted proxies before enabling the middleware, or every request will look like the balancer.
  4. Apply the middleware to one route first: rate.limiter:60,1 is the quickest smoke test, since middleware parameters are now parsed and validated rather than silently ignored.

First real use case: protect a login or token endpoint with a named preset (second=, minute=, hourly=, daily=, key=ip|user|route, name=) and assert the new X-RateLimit-* / Retry-After headers come back on both 200 and 429 responses. That single endpoint exercises windows, keying, headers and the 429 path.

Implementation Patterns

Middleware-driven limiting for the bulk of routes. Combine windows in one directive — rate.limiter:second=10,minute=120,hourly=2000 — and pick a key strategy with key=user, key=ip, or composites like key=ip+route for per-endpoint fairness. Named presets keep long directives out of route files.

Programmatic limits with RateLimit::with() when the limit depends on runtime data (tenant plan, user tier). Builders are immutable in v6, so chaining is safe to do inline and reuse:

$result = RateLimit::with(60, 1)->withUserId($user->id)->withName('api')->attempt($request);

Multi-window atomic checks via RateLimiter::attempt() or PendingLimit::attempt(), which return a LimitResult. Use this when you need to branch on the outcome (e.g. degrade gracefully, queue the job, return custom JSON) instead of throwing. Because all windows are evaluated in one Lua script, a rejected request no longer burns the larger windows — that alone removes a class of "client locked out for an hour" tickets.

Blocking + events for abuse handling. blockedFor() tells you how long a subject is blocked; RateLimitExceeded and IpBlocked let you ship metrics, Slack alerts or audit logs without patching the package. Prefer an event listener over log_errors for anything you actually want to query later.

Client contract: expose X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After. These now describe the most restrictive window, so a client backing off on Retry-After behaves correctly. Update any frontend or SDK that parsed the old header names.

Octane / long-running workers: safe by design in v6 — the singleton holds no request state and config is resolved through the Config\Repository contract. Still, don't cache a PendingLimit in a singleton of your own.

Cluster/TLS deployments: all windows for a subject share a hash tag, so a Redis cluster works without cross-slot errors. REDIS_URL, TLS and phpredis/predis are all supported through the Laravel Redis manager.

Gotchas and Tips

  • Header rename is silent breakage. X-RateLimit-Limit/Remaining/Reset replaced the old names; anything parsing them or asserting in tests will drift without an exception.
  • Middleware typos now throw. Parameters are validated, which is good, but a malformed directive that "worked" (was ignored) in v5 will fail loudly after upgrade. Audit route files for stale parameters.
  • Values below 1 are rejected — amounts, limits, intervals and block durations. A 0 meant to disable limiting no longer works; conditionally skip the middleware instead.
  • Whitelist semantics changed. Removing 10.0.0.0/8 means private ranges are limited by default. Combined with trust-all proxies, that's either a no-op or a shared-bucket outage. Configure TrustProxies deliberately and keep the whitelist loopback-only.
  • Requests without a client IP are no longer lumped into one shared bucket, so a misconfigured proxy shows up as unlimited traffic rather than a suspicious spike. Verify IP resolution in staging.
  • Choose on_redis_failure consciously. open fails open (availability over protection); closed returns 503. failure_backoff_seconds throttles error reporting to once per backoff window — tune it so an outage doesn't flood your log pipeline.
  • Logging defaults flipped. log_errors is false and, when enabled, writes a single warning with the path only. If you relied on error-level 429 logs with headers and input for debugging, move that to the RateLimitExceeded listener — and note the old behaviour was leaking request data.
  • Keys are escaped. withUserId('a:b') no longer collides with withUserId('a')->withName('b'). If you pre-computed keys or wrote dashboards parsing raw Redis keys, expect different strings.
  • headers is now true by default, so tests asserting an exact header set may fail.
  • Immutable builders: a RateLimit call can no longer inherit state from a prior call or from middleware. Code that mutated and reused a builder needs rewriting — usually a simplification, since you can now keep a base builder in a service class.
  • Config over connection settings: the old redis.* block is gone. A leftover published config with those keys is dead weight; verify the named connection actually resolves, especially if your default connection is cache rather than redis.
  • EVALSHA with EVAL fallback means no script-loading step in deploy, but a flushed Redis or a fresh replica will re-EVAL. Make sure your Redis ACLs allow EVAL/EVALSHA and SCRIPT LOAD, not just INCR.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource