graham-campbell/throttle
Rate limiting for Laravel by Graham Campbell. Throttle provides middleware and a facade to limit requests and actions by key, IP, or route, backed by your cache driver, with configurable hit limits and decay times. Ideal for APIs and forms.
Build vs. buy on a commodity control — and the answer is usually "neither." Laravel already ships ThrottleRequests middleware and the RateLimiter facade with named limiters, per-user/per-IP keys, and standard X-RateLimit-* / Retry-After headers. This package is a config-driven convenience wrapper over that primitive. The real product decision isn't "throttle or don't throttle" — it's "does a single-maintainer dependency earn its place by removing ongoing configuration sprawl?" Default to the framework; adopt the package only against a named gap, recorded in the decision log.
Turning rate limits into a customer-facing contract. The moment you throttle a public API you owe customers answers: what counts as a request, what a 429 body looks like, whether Retry-After is returned, how limits differ by plan, and how docs and SDKs describe it. Middleware is the cheap half; the error contract, tiering, and documentation are the expensive half — and that's a product and support-cost decision, not a technical one. Budget for it explicitly or the first enterprise customer discovers it for you.
Security baseline for abuse-prone routes. Throttling login, OTP, password reset, and webhook endpoints is the highest-leverage, lowest-cost control on the roadmap. A credential-stuffing incident costs orders of magnitude more than the effort to add per-route limits. Make "are these routes throttled, and at what limit?" a standing item in security review rather than a one-time ticket that closes and is forgotten.
Cache topology as a roadmap dependency, not a feature detail. Cache-backed counters mean throttling is only as good as the cache behind it. On per-node file or array cache, each server counts independently and the effective limit multiplies by node count — protection that looks correct in staging and is decorative in production. Adopting throttling implies a shared Redis/Memcached decision that belongs in the platform roadmap, sequenced before the middleware ships, never in the same release.
Platform upgrade sequencing. v11 supports only PHP 8.1–8.4 and Laravel 10–12. Adoption and the runtime upgrade are the same line item. If you're on PHP 8.0 or Laravel 9, this conversation is moot until the platform work lands — don't let a small package create a phantom dependency that distorts upgrade prioritization.
Deploy-time counter resets as planned events. The v11 switch to xxh128 key hashing changes derived throttle keys, so in-flight counters become unreachable and effectively reset on deploy. A client sitting at the edge of its budget gets a fresh allowance the instant you ship — for login, OTP, payments, or webhooks, that's a security-relevant event. Schedule such rollouts deliberately and watch 429 rates afterward; don't slip them in on a Friday.
Perimeter scope discipline. Application middleware sheds abusive clients; it does not absorb a volumetric DDoS. Adopting a throttle package must not close the WAF/edge conversation — they solve different problems and neither substitutes for the other.
Supply-chain risk tolerance for small dependencies. Zero known dependents and a low composite score aren't a code-quality verdict, but they are a market verdict. For a few hundred lines of middleware that's tolerable — provided you accept that your team is the QA and shouldn't expect community regression reports to surface breakage first.
Adopt when:
RateLimiter::for() plus throttle: for login and one API group first; adopt only if that falls short on a requirement you can name.Look elsewhere when:
throttle:60,1 and RateLimiter::for() cover the common cases with zero added dependency, zero upgrade surface, and zero supply-chain exposure. For most teams this is the correct default — be candid internally that the package's marginal value is convenience, not capability.RateLimiter definition or an API gateway, not a static config file.For executives: This is cheap insurance, not a differentiator. The proposal is to standardize how we throttle abuse-prone routes — login, password reset, webhooks, and public API endpoints — using Laravel's native rate limiter, with this package as an optional convenience layer only if it measurably reduces configuration sprawl. The customer-visible work is small and mostly documentation: consistent 429 responses and clearly described limit tiers. Two dependencies to flag: correctness requires a shared cache across servers, and it ties us to PHP 8.1+ / Laravel 10+. Recommendation: approve the security coverage now and fold the package choice into the platform upgrade rather than treating it as a standalone decision — the marginal value over the framework is convenience, and the framework option carries no added supply-chain risk.
For engineering: Treat this as configuration plus discipline, not a project. (1) Confirm the runtime matrix — PHP 8.1–8.4, Laravel 10–12 — in every environment including CI images and long-lived queue/Octane workers. (2) Confirm a shared cache store; if production runs multiple nodes on file or array cache, fix that before enabling anything (the array driver silently makes throttling per-process). (3) Verify TrustProxies and CDN header handling so client identity is correct — misconfigured, you either collapse all traffic into one bucket (self-inflicted global 429s) or make the key spoofable via X-Forwarded-For. (4) Prove the gap first with core RateLimiter::for() and throttle:; adopt the package only against a named shortfall, and record the reason. (5) If adopting, pin an exact version, roll it onto one low-risk route group, and wrap registration in a single config/service-provider abstraction so removal is a one-file change. (6) Sequence carefully: cache changes ship before the middleware, never in the same release, and the xxh128 re-keying means the first post-upgrade deploy resets in-flight budgets — ship it deliberately and watch 429 rates and cache health afterward. (7) Test concurrency, not single requests: fire N+1 parallel requests against a limit of N, assert the boundary holds and Retry-After is present. Exit criteria: identical enforcement across nodes, correctly shaped and documented 429s, and no auth or webhook route left unthrottled.
How can I help you explore Laravel packages today?