Laravel Packages

Laravel Quota: Usage Budgets for Calendar Periods

Published
Laravel Quota: Usage Budgets for Calendar Periods image

Laravel Quota is a package for tracking and enforcing cumulative usage limits. For example, you could enforce limits like 50 PDF exports a month, 1,000 API queries a day, a pool of AI credits per billing cycle, and more.

It's aimed at a different problem than Laravel's RateLimiter: instead of throttling bursts of traffic in a sliding window, it counts consumption against a budget that resets on a calendar boundary, and it can persist that count in the database rather than only in the cache.

Here's what the package gives you:

  • Calendar-aligned periods — define perMinute(), perHour(), perDay(), perWeek(), perMonth(), perYear(), or period($start, $end) for a custom window
  • A HasQuotas trait that puts quotas on any Eloquent model
  • Route middleware (quota:exports,50,month) that only consumes on a successful response
  • Cache or database backends, switchable per call
  • Atomic consumption - lock consumption around the work it meters
  • Enforce budgets - throw an HTTP 429 when the budget is spent
  • And more.

The Fluent API

A quota is a named counter scoped to an owner, with a limit and a period. Once you've described one, you can inspect it or spend against it:

use ZaberDev\Quota\Facades\Quota;
 
$builder = Quota::for('api_queries', $user)
->limit(1000)
->perDay();
 
$builder->used();
$builder->remaining();
$builder->isExceeded();
$builder->hasCapacity(10);
 
$info = $builder->consume(5);

The consume() method returns an immutable QuotaInfo DTO. If you'd rather fail hard than branch, the enforce() method throws an HTTP 429 when the budget is gone:

Quota::for('api_queries', $user)->limit(1000)->perDay()->enforce();

For work where a double-spend actually matters, block() wraps the callback in a lock so two concurrent requests can't both slip through the capacity check:

Quota::for('pdf_generation', $user)
->limit(50)
->perMonth()
->block(function () use ($pdfService) {
$pdfService->generate();
}, amount: 1, lockSeconds: 30);

Quotas on Eloquent Models

Add the HasQuotas trait and the same builder hangs off the model:

use ZaberDev\Quota\HasQuotas;
 
class User extends Authenticatable
{
use HasQuotas;
}
$user->quota('pdf_exports')->limit(25)->perMonth()->consume();
 
$user->quota('pdf_exports')->limit(25)->perMonth()->remaining();

On the database backend the records are polymorphic, so you can query a model's quotas like any other relation:

$activeQuotas = $user->quotas()
->where('period_end', '>', now())
->get();

Route Middleware

The quota middleware takes a name, a limit, a period, and an optional driver:

Route::post('/exports/generate', [ExportController::class, 'store'])
->middleware('quota:exports,50,month');
 
Route::post('/api/v1/query', [ApiController::class, 'query'])
->middleware('quota:api_query,1000,day,database');

The ordering is worth noting: capacity is checked before the route runs, but the quota is only consumed when the response comes back 2xx or 3xx. A request that 500s or fails validation doesn't cost the user anything.

Storage Backends

The default driver is cache, backed by whatever store you've configured — Redis and Memcached both work. The database driver writes to a quotas table instead, which is what you want when the count is tied to billing and can't evaporate with a cache flush. You can pick per call:

Quota::for('api_ping', $ip)->using('cache')->limit(5000)->perDay()->consume();
 
Quota::for('monthly_exports', $user)->using('database')->limit(50)->perMonth()->consume();

Quota::extend() registers a custom backend from a service provider if you need somewhere else to put the counters, and expired database rows can be cleaned up on a schedule:

use ZaberDev\Quota\Models\Quota;
 
Schedule::command('model:prune', ['--model' => Quota::class])->daily();

Installation

The package requires PHP 8.2 and supports Laravel 11, 12, and 13:

composer require zaber-dev/laravel-quota
 
php artisan vendor:publish --provider="ZaberDev\Quota\QuotaServiceProvider"
php artisan migrate

The config/quotas.php configuration file sets the default driver (QUOTA_DRIVER), the cache store and key prefix, the database table name, and whether events are dispatched.

You can find installation instructions and full documentation on GitHub.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

tinkerwell logo
Tinkerwell

Enjoy coding and debugging in an editor designed for fast feedback and quick iterations. It's like a shell for your application – but with multi-line editing, code completion, and more.

Visit Tinkerwell

The latest

View all →
PhpStorm 2026.2 Released image

PhpStorm 2026.2 Released

Read article
Laravel Doctor: Diagnose Your App With One Artisan Command image

Laravel Doctor: Diagnose Your App With One Artisan Command

Read article
CPX: The Composer Package Executor for PHP image

CPX: The Composer Package Executor for PHP

Read article
Laravel AI SDK Adds Human-in-the-Loop Tool Approval image

Laravel AI SDK Adds Human-in-the-Loop Tool Approval

Read article
Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals image

Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals

Read article
Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs image

Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs

Read article