hanzala-afzal / laravel-dynamic-throttle
Runtime-managed, identity-aware rate limiting for Laravel: change limits without a deploy, per user or token, and dry-run them first.
Package info
github.com/hanzala-afzal-dev/laravel-dynamic-throttle
pkg:composer/hanzala-afzal/laravel-dynamic-throttle
Requires
- php: ^8.3
- laravel/framework: ^12.69.0 || ^13.30.0
Requires (Dev)
- larastan/larastan: ^3.13
- laravel/pint: ^1.24
- orchestra/testbench: ^10.6 || ^11.0
- pestphp/pest: ^4.1 || ^5.0
- pestphp/pest-plugin-laravel: ^4.0 || ^5.0
- phpbench/phpbench: ^1.7
- predis/predis: ^2.3 || ^3.0
- rector/rector: ^2.1
Suggests
- ext-apcu: Enables the per-server L1 rule cache on PHP-FPM (see docs/adr/0007).
- ext-redis: Needed by the redis driver when you use the phpredis client.
- predis/predis: Needed by the redis driver when you use the predis client instead of ext-redis.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-21 05:54:04 UTC
README
Important
Public beta. Everything planned for 0.1.0 is in, but names and options may still change, and the changelog will say how to upgrade. Try it in staging first, and tell us what breaks or confuses you.
Dynamic Throttle for Laravel lets you change your app's rate limits while it runs: per IP, user, API token, header or tenant, with a dry-run mode, exemptions, emergency switches and a dashboard.
Why
Laravel's throttle middleware works for limits you know up front. In production, you learn the right limits later, and in a hurry: a partner's integration starts getting 429s, a bot hammers /login at 2 a.m., or you want a stricter limit without knowing who it would block. With throttle, each fix is a code change and a deploy, and you still can't see who is blocked.
Dynamic Throttle makes limits data. Rules live in your database, you change them from a dashboard, Artisan or code, and every server applies the change within seconds.
| You want to | Laravel's throttle |
Dynamic Throttle |
|---|---|---|
| Change a limit | Edit code and deploy | Edit the rule; servers apply it within 2 seconds |
| Try a limit before it blocks anyone | Not possible | Dry-run: the rule counts, never blocks, and reports who it would block |
| See who is blocked | Add your own logging | A live feed, and each rule's top offenders |
| Let a partner or a user through | A code change | An exemption, with an end date |
| Lift every limit during an incident | A deploy | dynamic-throttle:bypass on --for=30m, which ends by itself |
| Limit per tenant or plan | A custom limiter closure | A custom scope and one rule |
| Know why a request was blocked | Read the code | The Explain page, or dynamic-throttle:explain |
| Keep serving when Redis is down | An exception per request | Requests go through, or get a 503 on the rules you choose |
Requests never read the rules from the database: they are compiled into your cache. On local Redis, the middleware adds about 0.8 ms per request at p50 with APCu, against 1.0 ms for Laravel's throttle and 0.6 ms for its throttleWithRedis() (performance). It is not a firewall or bot detection, and it does not replace edge rate limiting such as Cloudflare or Nginx. Use it for limits that need to know who the client is.
The dashboard
It ships with the package at /dynamic-throttle, behind your gates, with nothing to build: Overview, Rules, Exemptions, Activity (a live feed of blocks), Explain, Audit log and Settings, in light and dark themes.
Requirements
- PHP 8.3+, and Laravel 12.69+ or 13.30+.
- A counter store: Redis 6.2+ or Valkey 7.2+ is best, and the live feed and offenders need one. Memcached and the database store work too, but not
fileorapcin production. - On PHP-FPM, the APCu extension, so a server's workers share the rules.
Installation
1. Install the beta:
composer require hanzala-afzal/laravel-dynamic-throttle:^0.1@beta
2. Create the tables, and compile the rules once. After this, saving a rule compiles them, and so does the scheduler.
php artisan vendor:publish --tag=dynamic-throttle-migrations php artisan migrate php artisan dynamic-throttle:compile
3. Attach the middleware to the routes rules may limit, in bootstrap/app.php. Routes without it are never limited.
->withMiddleware(function (Middleware $middleware) { $middleware->appendToGroup('api', 'dynamic.throttle'); })
4. Say who may use the dashboard, in a service provider's boot(). Until you do, only the local environment gets in.
use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::define('viewDynamicThrottle', fn (User $user) => $user->is_admin); Gate::define('manageDynamicThrottle', fn (User $user) => $user->is_admin);
5. Run Laravel's scheduler. It recompiles the rules every five minutes and stores the traffic numbers every minute.
Then check your setup with php artisan dynamic-throttle:health.
Usage
Add a rule in the dashboard under Rules → New rule, from a template such as "Login" or "API per user". New rules start in dry-run: they count requests and show who they would block, but block no one until you promote them.
Or from code:
use Afzal\DynamicThrottle\Models\Rule; Rule::create([ 'name' => 'API per user', 'pattern' => 'api/*', 'scope' => 'user', // ip, user, api_token, header, global or custom 'max_attempts' => 60, 'window_seconds' => 60, ]);
Each user now gets 60 requests a minute under /api. The 61st gets Laravel's own 429, with Retry-After and X-RateLimit-* headers.
| Command | What it does |
|---|---|
dynamic-throttle:health |
Looks for setup mistakes. --strict fails on a warning too |
dynamic-throttle:explain GET /api/orders --user=42 |
Shows why a request would be allowed or limited, without counting it |
dynamic-throttle:status |
Shows what the servers apply now, and until when |
dynamic-throttle:bypass on --for=30m |
Lifts every limit until the end time |
dynamic-throttle:scale 2 --for=2h |
Multiplies every limit: 2 doubles them, 0.5 halves them |
dynamic-throttle:disable-rule 12, enable-rule 12 |
Switches one rule off or back on |
dynamic-throttle:list |
Lists every rule with its id |
dynamic-throttle:compile |
Recompiles the rules now |
Documentation
The guide has the details:
- Rules: every attribute, examples, your own scope, and exemptions
- An endpoint that costs money, such as one that calls an AI model: a limit per user or per plan, tried in dry-run first
- Dry-run rules, events and testing your app with
DynamicThrottle::fake() - Configuration, attaching the middleware, and storage and runtimes
- The dashboard, health checks and during an incident
- The runbook: Redis is down, the manifest was evicted, a bad rule blocks
/api, or an emergency change was left on
Beta feedback
What helps most:
- Did it install cleanly on your Laravel and PHP versions, cache store and runtime (PHP-FPM, Octane, Vapor)?
- Did a rule do something you did not expect? Paste the output of
php artisan dynamic-throttle:explain. - Was anything in the dashboard or the docs unclear?
Report bugs in issues, and bring questions and ideas to discussions. Report security issues privately, as SECURITY.md explains, never in a public issue.
License
MIT, © 2026 Hanzala Afzal. See LICENSE, CONTRIBUTING.md and the changelog.
An independent package, not affiliated with or endorsed by Laravel. "Laravel" is a trademark of its respective owner.
