Platform admin
Plan grants & trials (assign for a period)
A plan grant lets a super-admin give any workspace a plan for a custom period — with no payment and no credit card. It's the mechanism behind no-card free trials, beta access, promos, strategic partnerships, and manual onboarding. When the period ends the customer keeps their data but is prompted to upgrade to keep using the platform.
How to grant a plan
- Sign in as a super-admin and open
/admin/workspaces. - Click the workspace you want to grant access to.
- In the Assign plan (complimentary) card, pick the plan, a duration (7 / 14 / 30 / 90 days, or a custom expiry date), and an optional note (e.g. "Beta partner", "Q3 promo").
- Click Grant plan.
By default the grant starts immediately and the card shows "Currently granted until <date>". You can re-grant at any time to extend or change the plan — it overwrites the workspace's current subscription row.
Scheduling a future start
Set Start to "On a future date" and pick the
day the grant should begin. Until then the grant is inactive:
the workspace stays on whatever plan it has now, and the card shows
"Scheduled to start <date>". On the start date the plan
switches on automatically (an hourly app:activate-plan-grants
job promotes the scheduled row to trialing and
flips the workspace's plan_id), and it then runs for the
chosen duration. Duration presets (7 / 14 / 30 / 90 days) count from the
start date, not from today, so "30 days starting next Monday"
expires 30 days after that Monday. Use this for partnerships, promos, or
onboarding lined up in advance.
What happens under the hood
A grant reuses the same machinery as a self-serve trial — there's no separate code path:
- The workspace's
plan_idis set to the granted plan, soeffectivePlan()unlocks that plan's quotas and features immediately. - The subscription ledger row is written as
trialingwithcurrent_period_end= the grant's expiry, plusgranted_by_user_id(audit) and the gatewayadmin. - While the grant is live the customer uses the platform normally —
hasActivePaidAccess()stays false (it's complimentary, not paid), but feature gating runs off the effective plan, so they get the real thing. - Once
current_period_endpasses, the existingEnsureTrialActivewall redirects every/app/*page to the billing page with an "upgrade to continue" prompt — the same wall a self-serve trial hits.
The expiry email
The wall is passive (it fires on the customer's next request). To
proactively tell a customer their access ended, the
app:process-plan-expirations command runs daily (scheduled
in routes/console.php, tickled by the platform cron). It
finds trialing subscriptions whose period just ended and
emails the owner a branded "your access has ended — upgrade to continue"
notification (also dropped into the in-app bell). It stamps
expiry_notified_at so it never double-sends. The wording
differs slightly for a grant ("your complimentary <plan> access
has ended") versus a self-serve trial ("your <plan> free trial has
ended"), but the action is identical.
Self-serve trials
A time-limited no-card trial already works without any grant: mark a
plan as a trial (is_trial +
trial_days) and set it as the signup default. New
registrations land on it as trialing for
trial_days, see a countdown on the billing page, and hit the
same wall + expiry email when it lapses. A grant is the manual,
admin-issued version of that — for any plan, any length, any workspace.
Safety
- Super-admin only; the endpoint is existence-hidden (404) to everyone else.
- You can't grant against the workspace you're currently signed into (it would wall you out of the admin surface) — switch workspaces first.
- The grant's expiry must be in the future; the plan must exist.