Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Quota Laravel Package

zaber-dev/laravel-quota

Laravel Quota tracks and enforces application-level usage budgets across daily, weekly, monthly, or custom periods. Store quotas in cache or database, attach them to Eloquent models, protect routes with middleware, and extend with custom backends. Complements RateLimiter.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is an idiomatic Laravel library: a service provider, a Quota facade backed by a Manager/Driver abstraction, immutable QuotaInfo DTOs, a HasQuotas trait with a polymorphic relation, a pre-registered quota middleware alias, typed domain events, and Prunable integration. It deliberately does not overlap with RateLimiter; RateLimiter/throttle stays for burst protection and DDoS, while this package owns cumulative budgets over calendar windows. That separation is the correct architectural framing for our stack, and it means adoption is additive rather than a refactor of existing throttling.

Integration feasibility. Very high. Composer install, one vendor:publish, optional php artisan migrate, add HasQuotas to models that meter usage, and annotate routes. Everything else is opt-in: driver selection per call (using()), events, pruning schedule. The fluent surface (Quota::for('action', $target)->limit(50)->perMonth()->consume()) and the middleware syntax (quota:exports,50,month,database) are declarative enough that non-authoring engineers can read enforcement rules from routes/api.php without a controller dive. Preconditions are only Laravel 11/12/13+ and PHP 8.2+, both cheap to verify.

Technical risk. This is a young, single-vendor, low-adoption package (110 stars, 0 dependents, first releases in 2026). The risks are not conceptual but behavioural, and several are undocumented in the README:

  • Atomicity per driver is asserted, not specified. block() provides a time-boxed advisory lock (lockSeconds), not a guarantee. If the callback outlives the lock, a second request can acquire and both consume on success. For expensive AI/video/export jobs we must set lockSeconds well above p99 latency, not the default.
  • Cache driver correctness depends on the underlying store. Redis/Memcached support atomic increments; the array, file, and database cache stores do not behave equivalently under concurrency. The package does not publish a supported-store matrix, so read-modify-write overage is a plausible failure on a misconfigured store.
  • Driver split-brain. The same action enforced with cache in one code path and database in another silently maintains two independent counters. Nothing prevents this; only convention does.
  • Key stability is a hidden contract. Keys embed the morph-resolved class name (action:App_Models_User:12:{periodStart}:{periodEnd}). Renaming a model, introducing Relation::enforceMorphMap(), or changing the app timezone changes key derivation and effectively resets historical usage for that period.
  • No idempotency key on consume(). A retried queue job or a duplicated webhook double-charges a user's budget. There is no dedupe/transaction id; we must design around it.
  • Middleware consumes on 2xx/3xx. Correct for validation failures (no charge on 422), but a redirect-heavy flow or a client retry loop can consume twice for one user intent.
  • Billing boundary. The package stores counters, not entitlements. Plan definitions, proration, and invoiceable overage stay in the billing system. Cache-driver usage is not auditable; only database records are, and those are pruned.
  • Operational quirk: the service provider syncs an AI skill into .ai/skills/ on boot when Laravel Boost is present. That means the package writes into the repository directory at runtime — a dirty-working-tree and CI-diff hazard on build agents.

Key questions for the maintainer / spike:

  1. Does the cache driver use atomic INCR+EXPIRE (single round trip) or read-modify-write? Which stores are formally supported?
  2. Does the database driver use upsert/row locks/transactions, and what is the expected behaviour under N concurrent consume() calls on one key?
  3. Fail-open or fail-closed when Redis or the DB is unavailable? Is that configurable?
  4. Is block()'s lock released on exception, and what error surfaces on lock-acquisition timeout — 429 or 500?
  5. Is the cache key TTL anchored to periodEnd, so stale periods self-expire without cron?
  6. What is the versioning/BC policy, release cadence, and is there a documented upgrade path across Laravel majors?
  7. Does the middleware support token/API-key targets, or only user() ?? ip()?

Integration Approach

Stack fit. Strong for any Laravel 11/12 service with Redis and a relational DB. The natural mapping is: Redis cache driver for high-frequency, low-stakes budgets (per-minute/hour API calls, ping-style limits); database driver for anything a customer can see, dispute, or be charged for (monthly exports, AI credits, seat caps). That dual-driver story is the package's main product advantage and should drive the adoption design.

Migration path. Do not attempt a big-bang replacement of existing counters. Phased:

  1. Pilot — one non-billing endpoint (e.g., CSV export) behind cache driver, no user-facing messaging. Validates install, middleware, and 429 rendering.
  2. Instrument — add HasQuotas to User (and Team/Tenant if multi-tenant) and migrate one internal ad-hoc counter column to a perMonth() quota. Compare old and new counts in parallel for a full period before deleting the old column.
  3. Billing-adjacent — move the durable budgets to database driver, wire QuotaConsumed/QuotaExceeded to our metrics/logging sink, and surface QuotaInfo in API responses via headers so the frontend stops hardcoding limits.
  4. Decommission — retire bespoke counters and any RateLimiter usage that was actually doing monthly budgeting (a common mis-implementation worth auditing for).

Compatibility. Keep throttle middleware in place — this package is explicitly not burst protection. Ensure Sanctum/Passport auth is resolved before CheckQuota in the middleware priority list, otherwise the middleware falls back to IP and per-user budgets become per-NAT-IP budgets. Verify timezone handling (app.timezone + NTP across nodes) before enabling perDay()/perHour() boundaries; clock skew across app servers produces different periodStart keys and inconsistent limits. If enforceMorphMap() is used anywhere, lock it in before first write, since keys are derived from resolved morph names.

Sequencing. Publish config and migrations in the same PR that pins the version. Decide the driver per action as an explicit table in an ADR — this is the artifact that prevents split-brain. Schedule model:prune for the Quota model before the DB driver's first period rolls over, not after. Then wire events, then observability, then billing-adjacent quotas. Budget roughly 1–2 days for the pilot and 1–2 sprints to reach production-grade coverage of the top handful of metered actions.

Operational Impact

Maintenance. Small footprint: one composer dependency, one published config, one migration, one scheduled prune command. The cost is not maintenance labour but version discipline — pin to an exact version, review changelogs on bump, and treat any change to key derivation, driver semantics, or middleware consumption rules as breaking regardless of the declared SemVer. Because the licence is MIT and the surface is small, an internal fork is a realistic contingency; budget a day of spike time to confirm the driver contracts (QuotaDriverContract) are clean enough to subclass if we need DynamoDB or a stricter atomic implementation via Quota::extend().

Support. No dependents, small star count, effectively a single-vendor package: community answers will be thin and forum/Stack Overflow coverage is near-zero. Practically this means our own engineers are tier-1 support. Mitigating factors are unusually good in-repo documentation (README plus LEARN.md plus a machine-readable SKILL.md), so the knowledge is at least transferable and internalisable. The flip side: agents and engineers following SKILL.md will write consistent code, which reduces review friction but also means a documented anti-pattern propagates quickly. Keep a short internal conventions doc (which actions exist, which driver each uses, who owns the limit values) as the source of truth that overrides the package docs.

Scaling. Cache driver scales horizontally with Redis; the only concern is key cardinality per user×action×period, which is trivial. Database driver is the scaling question: one durable row per target×action×period, updated once per consume, so hot keys (a large tenant's every-request budget) create row-level write contention. Keep per-request-granularity budgets on cache and reserve the DB for coarser, human-scale budgets (monthly exports, seats). Watch the quotas table's growth and index the pruning predicate (period_end); an unscheduled prune is the most likely slow-burn incident, converting a correctness feature into an unbounded table. Multi-region deployments need a shared Redis or the cache quotas become per-region — a silent 2× limit.

Failure modes to design for.

  • Cache flush/eviction or Redis failover → quotas silently reset. For anything that gates revenue, this is a revenue leak and a support-ticket generator. Database driver for money.
  • Redis or DB outage → we do not know from the docs whether enforcement fails open (unlimited free usage) or closed (site-wide 429s). Both are bad in different ways; test it explicitly and get an answer before rollout.
  • Lock overrun on block() → double-consume on slow jobs. Set lockSeconds from measured p99, not defaults.
  • Retried jobs/webhooks → double-consume because consume() has no idempotency key. Wrap consumption in the job's own idempotency guard.
  • Clock skew / timezone change → users see unexpected resets or double periods at boundaries.
  • Renamed model or new morph map → historical usage appears to vanish mid-period.
  • Service-provider file sync into .ai/skills/ → dirty build artifacts and noisy CI diffs; exclude the path in CI checks or remove Boost integration in build images.

Ramp-up. Fast for a Laravel-fluent engineer: the API is self-describing and the middleware is a one-liner, so a mid-level developer is productive within a few hours and the pilot needs no dedicated training. The real ramp-up cost is conceptual, not syntactic: engineers must internalise that this package is not rate limiting, that driver choice is a correctness decision rather than a performance tweak, and that the DB driver's records are the only auditable evidence of usage. Plan a 30-minute team walkthrough covering those three points plus the failure modes above; that is a better investment than any amount of documentation reading.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox