Why I Built an Application-Level Quota Manager for Laravel
Laravel provides excellent tools for request throttling through its built-in RateLimiter. But while building applications, I kept running…
Why I Built an Application-Level Quota Manager for Laravel

Laravel provides excellent tools for request throttling through its built-in RateLimiter. But while building applications, I kept running into a different problem.
I didn’t want to limit requests.
I wanted to limit usage.
Think about features like:
- Monthly PDF exports
- AI credits and prompt allowances
- API request budgets
- Storage upload limits
- Team creation caps
- Subscription plan limits
- Weekly newsletter quotas
- Daily transaction budgets
These aren’t traditional rate-limiting problems. They’re examples of application-level quotas, where a user or organization is allocated a fixed amount of usage over a defined period.
The Problem
My first implementations looked like many Laravel applications today.
A database table with counters.
Some Cache::increment() calls.
A few Carbon calculations.
Maybe a scheduled command to reset monthly limits.
It worked.
Until it didn’t.
Soon every feature had its own implementation.
Different period calculations.
Different key formats.
Different reset logic.
Different concurrency handling.
And whenever I needed to add another quota-based feature, I found myself copying the same ideas yet again.
I wanted something reusable.
What I Wanted
Before writing any code, I wrote down a few goals.
The solution should:
- Support both cache and database storage
- Understand calendar periods naturally
- Work with Eloquent models
- Support arbitrary targets such as users, teams, API tokens, and IP addresses
- Be usable from controllers, jobs, commands, and middleware
- Prevent concurrent over-consumption
- Allow custom storage backends
- Feel natural to use
Most importantly, I wanted the API to read almost like English.
Quota::for('monthly_exports', $user)
->limit(50)
->perMonth()
->consume();
Or simply inspect what’s left.
$remaining = Quota::for('monthly_exports', $user)
->remaining();
That became the foundation of the package.
Calendar Periods Shouldn’t Require Date Math
One thing I noticed was how often I was writing Carbon logic just to calculate period boundaries.
Start of month.
End of month.
Start of week.
Custom billing cycles.
Every project had slightly different implementations.
Instead, I wanted period management to become part of the API.
Quota::for('api_requests')
->limit(1000)
->perDay();
Or:
Quota::for('exports')
->limit(50)
->perMonth();
Or even:
Quota::for('billing')
->period($start, $end);
The application shouldn’t have to worry about timestamps.
It should simply express the business rule.
Using Laravel’s Manager Pattern
Like my other packages, I built Laravel Quota around Laravel’s Illuminate\Support\Manager.
Instead of coupling the package to a single storage implementation, the manager resolves interchangeable storage backends.
Today the package supports:
- Cache
- Database
Adding another backend follows Laravel’s familiar extend() pattern, making it straightforward to integrate services such as DynamoDB or MongoDB.
This separation keeps the public API identical regardless of where quota data is stored.
Treating Models as First-Class Targets
Another thing that always felt repetitive was manually constructing quota keys.
Instead of writing something like:
Cache::increment("exports:user:{$user->id}");
I wanted the package to understand models directly.
$user->quota('exports')
->limit(50)
->perMonth()
->consume();
The package automatically resolves unique identifiers for Eloquent models, scalar values, API tokens, IP addresses, and other supported targets.
No manual key generation.
No duplicated naming conventions.
Middleware Shouldn’t Punish Validation Errors
One design decision I spent quite a bit of time thinking about was middleware behavior.
Many quota implementations deduct usage before the controller executes.
Imagine generating a report.
You submit the request.
Validation fails.
You fix a typo.
Now you’ve already lost one of your monthly exports.
That never felt right.
Instead, Laravel Quota only consumes units after successful (2xx) or redirected (3xx) responses.
If validation fails or an exception occurs, users can immediately correct the problem without losing part of their allowance.
It’s a small detail, but one that makes the experience much more user-friendly.
Concurrency Matters
Another issue appears when multiple requests arrive simultaneously.
Suppose a user has one remaining export.
Two requests hit the server at nearly the same time.
Without synchronization, both requests might pass the capacity check before either updates the counter.
Suddenly the user has consumed more than their allowed quota.
To avoid that, Laravel Quota includes atomic execution through block().
Quota::for('monthly_exports', $user)
->limit(50)
->perMonth()
->block(function () {
// Generate export...
});
The package temporarily locks the operation, executes the callback, and only deducts quota after successful completion.
That keeps usage accounting consistent even under heavy concurrency.
More Than Just a Counter
Over time the package grew to include the features I repeatedly needed across projects:
- Cache and Database storage backends
- Fluent builder API
- Calendar-based periods
- Atomic execution
- Native Eloquent integration
- Route middleware
- Immutable DTOs
- Automatic database pruning
- Event dispatching
- Custom storage backend support
The goal wasn’t to replace Laravel’s excellent RateLimiter.
It was to solve a different class of problems: application-level quota management and usage tracking.
Open Source
I eventually decided to package everything into Laravel Quota and release it as open source.
If you’d like to try it, explore the code, or share feedback on the API or architecture, I’d genuinely appreciate it.
GitHub:
https://github.com/zaber-dev/laravel-quota
Open source improves through discussion, and I’d love to hear how you’re currently handling quotas, usage budgets, and subscription limits in your own Laravel applications.
메타데이터
- post_id
- 6fe0c4e1310d
- slug
- why-i-built-an-application-level-quota-manager-for-laravel-6fe0c4e1310d
- url
- https://medium.com/@zaberdev/why-i-built-an-application-level-quota-manager-for-laravel-6fe0c4e1310d
- canonical_url
- https://medium.com/@zaberdev/why-i-built-an-application-level-quota-manager-for-laravel-6fe0c4e1310d
- author_url
- https://medium.com/@zaberdev
- status
- ok
- fetched_at
- 2026-07-15 10:15:25