B Blengi docs

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

  1. Sign in as a super-admin and open /admin/workspaces.
  2. Click the workspace you want to grant access to.
  3. 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").
  4. 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_id is set to the granted plan, so effectivePlan() unlocks that plan's quotas and features immediately.
  • The subscription ledger row is written as trialing with current_period_end = the grant's expiry, plus granted_by_user_id (audit) and the gateway admin.
  • 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_end passes, the existing EnsureTrialActive wall 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.

Related