Access and grants

What a plan or product unlocks, and granting access by hand or from your server.

Access

This is the paywall. A plan or a product unlocks a collection when you choose it by name in that collection's Unlocked by row on its page in Data. Paying for the plan opens it; cancelling, lapsing or a full refund takes it back. The server refuses everyone else, and your app writes no check at all.

// On the Payments page: a plan named "pro" with a price
// In Data: "reports" → Unlocked by → tick "pro (plan)"

// Your app reads, catches entitlement_required and shows the upgrade prompt:
try {
  const { records } = await g.collection("reports").list()
} catch (err) {
  if (err.code === "entitlement_required") showUpgrade(err.requires)  // "access:pro", the plan's access key (see below)
}

Call plans and products whatever your business calls them (pro, Film X, Course: Foundations). Gemmein attaches no meaning to the names, so a new tier needs no Gemmein release. A collection can be unlocked by several plans or products at once ("anyone on pro or studio, or who bought Midnight Pack"), and any one of them opens it. A person can hold access for several reasons at once, such as a plan, a lifetime purchase and a manual grant from the dashboard, and it ends only when the last one does.

Access keys. Each plan and product carries one access key, generated from the name when it is first saved: plan "pro" → access:pro, product "Midnight Pack" → access:midnight-pack. A rename keeps the key, so nobody loses access. It is what err.requires carries (one key, a string; if several plans open the collection, it names the first one) and what g.purchases.mine() lists under grants. The dashboard shows it only as a muted hint beside the name, and nobody types it.

Access is set per collection. "Three free lessons, the rest premium" means two collections, one open and one locked. "Must hold both" cannot be expressed; that is a second collection. A locked public_read or community collection becomes authenticated access: an anonymous reader is told to sign in (never which plan they lack).

What your app can see. It reads a person's purchases and what each one granted (g.purchases.mine()) and their subscription (g.subscriptions.mine()). By-hand grants are not listed to the app, and a locked read succeeds. So never rebuild the paywall client-side from the lists you can see; let the server refuse, and show the upgrade prompt on entitlement_required. To give someone access, grant it on their page in the dashboard; it is never code.

Grants

Every bit of access a person holds is a grant: one access key, one reason, a start and maybe an end. Grants have seven sources in three families. The family decides who can create a grant, what ends it, and whether it counts as revenue on your dashboard.

FamilySourcesWho writes itEnds whenCounts as revenue
Purchase-tiedsubscription, purchaseOnly a payment: Stripe's signed webhook, or the relay action (fulfil_product, grant_plan) set as how the product or plan is sold. A bank debit counts once it clears. Never you by hand, never your appA subscription grant: the subscription cancels, lapses or is revoked by its relay. A purchase grant: a full refund (a partial refund leaves it)Yes, the only grants that do
By handby hand, trial, promotion, migration (people you brought over from somewhere else)You, from a person's page, or your server with a secret key (grantAccess), choosing the plan or product by name, with an optional end date and a reasonOn its end date, or when you revoke itNo
By relayrelayA relay, when its trigger fires: a provider's signed webhook, a schedule, or a record change. No secret key can create oneOn the date the action named, or when you revoke itNo
  • A grant is never edited. An extension is a new grant, so the reason on every grant stays true.
  • You can end a purchase-tied grant by hand from the person's page. That stops the access and refunds nothing; money moves only in Stripe.
  • A refund never changes a by-hand or relay grant, and revoking a by-hand grant never triggers a refund.
  • A relay grant reads "granted by relay <name>" on the person's page.

Which grant to use:

  • "Give them a free month": a trial grant with an end date.
  • "Launch deal, first 50 get pro": a promotion grant.
  • "Support fix, they were double-charged": a by hand grant with the reason written down.
  • "They paid on my old platform": a migration grant, so the record says why.
  • "They paid through GoCardless": a relay grant, written when the provider's signed webhook lands.
  • "They paid": never by hand, because Stripe writes it, so revenue reflects only money that arrived.
Was this page helpful?

Last updated