- What is zaber-dev/laravel-quota used for, and how is it different from Laravel's RateLimiter?
- Laravel Quota manages cumulative application-level usage budgets over calendar windows such as daily, weekly, monthly, or custom periods, for example 50 monthly exports or AI credits per user. It deliberately complements Laravel's RateLimiter rather than replacing it: RateLimiter and throttle remain the right tools for burst protection and DDoS defence, while this package owns longer-running usage accounting. Typical uses include PDF export budgets, prompt limits, API request quotas, team creation caps, and storage thresholds.
- How do I install and set up Laravel Quota in a Laravel application?
- Install it with Composer using composer require zaber-dev/laravel-quota. Then run the vendor:publish step to publish its configuration and, if you plan to use the database driver, run php artisan migrate to create the quota tables. The service provider, Quota facade, and quota middleware alias are registered automatically, and everything else such as driver selection, events, and pruning is opt-in. Add the HasQuotas trait to any models you want to meter.
- Which Laravel and PHP versions does Laravel Quota support?
- The package requires PHP 8.2 or higher and supports Laravel 11, 12, and 13+. Those two constraints are the main preconditions to verify before adopting it, and both are cheap to check in composer.json. If your application is still on Laravel 10 or PHP 8.1, this package is not a fit without upgrading first.
- Which storage backends does Laravel Quota support, and should I use cache or database?
- The package supports Redis, Memcached, and database storage, and you can select a driver per call with using(). The database driver gives you auditable, persistent records that can be pruned, while the cache driver is lighter but not auditable and depends on the underlying store for atomic increments. The README does not publish a formal supported-store matrix, so avoid array, file, or database cache stores for enforcement under concurrency and validate your chosen store in a spike. Also avoid mixing drivers for the same action, because that silently maintains two independent counters.
- How do I attach quotas to an Eloquent model such as User or Team?
- Add the HasQuotas trait to the model, which provides a polymorphic relation for quota records. You can then pass the model as the target in the fluent API, for example Quota::for('monthly_exports', $user)->limit(50)->perMonth()->consume(). Be aware that quota keys embed the morph-resolved class name, so renaming the model, adding Relation::enforceMorphMap(), or changing the app timezone can change key derivation and effectively reset usage for the current period.
- How do I enforce a quota on a route using Laravel Quota middleware?
- The package pre-registers a quota middleware alias, so you can annotate routes declaratively with syntax such as quota:exports,50,month,database. That keeps enforcement rules readable in routes/api.php without digging into a controller. The middleware consumes quota on 2xx and 3xx responses, which correctly avoids charging users for validation failures such as 422, but redirect-heavy flows or client retry loops can consume twice for a single user intent. For anything expensive, review how retries interact with your route middleware.
- Can I define custom quota periods beyond daily, weekly, and monthly?
- Yes. The package ships with daily, weekly, and monthly calendar periods and supports custom period windows through its period API. Calendar anchoring means limits reset at period boundaries rather than sliding windows, which is usually what you want for monthly budgets and billing cycles. Confirm how your custom boundaries interact with timezone settings, since key derivation includes period start and end.
- Is consume() atomic and safe under concurrent requests?
- The package asserts per-driver atomicity but the README does not specify the implementation, so treat it as something to verify rather than assume. Redis and Memcached can support atomic increments, but array, file, and database cache stores do not behave equivalently under concurrency, which makes read-modify-write overage plausible on a misconfigured store. The database driver's use of upserts, row locks, or transactions is also undocumented. Before relying on it for expensive AI, video, or export jobs, run a spike with N concurrent consume() calls on one key and ask the maintainer about atomicity, fail-open or fail-closed behaviour, and lock release on exceptions.
- How do I prevent retried queue jobs or duplicate webhooks from double-charging a quota?
- There is no idempotency key or transaction ID on consume(), so a retried job or duplicated webhook will consume the budget again. Design around this by charging in a single, non-retried place where possible, or by recording your own dedupe identifier and checking it before calling consume(). For queued work, make the consumer job safe to retry and consider consuming only after the external side effect succeeds.
- How do I prune old quota records, and is Laravel Quota suitable as a billing system?
- The package integrates with Laravel's Prunable support, so database quota records can be cleaned up on a schedule rather than accumulating indefinitely. Cache-driver usage is not auditable at all, and database records are pruned, so neither should be treated as a financial ledger. The package stores counters, not entitlements: plan definitions, proration, and invoiceable overage belong in your billing system. Also note the package is young and single-vendor with low adoption, so budget for maintenance risk and verify behaviour like block() lock duration, which is an advisory time-boxed lock rather than a hard guarantee.