crumbls/subscriptions v2.0.1
Usage tracking
For numeric / quota features, the package tracks per-subscription consumption in the plan_subscription_usage table and resets on a configurable interval.
Recording usage
$sub->recordFeatureUsage('api-requests'); // increment by 1
$sub->recordFeatureUsage('api-requests', 5); // increment by 5
$sub->recordFeatureUsage('api-requests', 10, false); // SET to 10 (not increment)
$sub->reduceFeatureUsage('users', 1);
The third argument is $incremental. Default is true (add to current). Set false to overwrite -- useful for "current user count" style features where the source of truth is a count(*) somewhere else.
If the feature isn't attached to the subscription's plan, this throws Crumbls\Subscriptions\Exceptions\UnknownFeatureException. The exception exposes featureSlug and plan as readonly properties so you can render an upsell prompt or a plan-mismatch error.
Querying
$sub->canUseFeature('api-requests'); // bool: used < value
$sub->getFeatureUsage('api-requests'); // int: current count
$sub->getFeatureRemainings('api-requests'); // int: value - used
$sub->getFeatureValue('api-requests'); // string: raw pivot value
canUseFeature is the right check before allowing the user to do the gated thing.
Resets
A feature with a resettable_period and resettable_interval automatically resets its usage counter at the end of each interval:
Feature::create([
'slug' => 'api-requests',
'resettable_period' => 1,
'resettable_interval' => 'month',
]);
The usage row stores a valid_until column. When you call recordFeatureUsage after valid_until has passed, the count resets to 0 and valid_until advances by one interval. Resets are lazy -- they happen on next access, not via a scheduled job.
A resettable_period of 0 means "never reset" -- use this for running counts like "current user count" or "current project count" where you'll typically reduceFeatureUsage when the resource is removed.
Pattern: enforce a hard limit
$sub = $tenant->currentSubscription();
if (! $sub->canUseFeature('users')) {
return back()->withErrors(['users' => 'User limit reached. Upgrade to add more.']);
}
DB::transaction(function () use ($tenant, $sub, $request) {
$tenant->users()->create($request->validated());
$sub->recordFeatureUsage('users');
});
Pattern: sync from source of truth
For "current count" features, recompute periodically rather than incrementing on every event:
$sub->recordFeatureUsage('users', $tenant->users()->count(), incremental: false);
Run this on a daily() scheduled job, or after bulk imports.
Pattern: rate-limited API endpoint
Route::middleware('can-use-feature:api-requests')->post('/api/things', function () {
$sub = auth()->user()->currentSubscription();
$sub->recordFeatureUsage('api-requests');
// ... handle request ...
});
The middleware short-circuits with a 403 if the feature is unavailable; the controller only runs if there's quota.