resource_kinds
Each kind of place a connector can gate — its labels, its icon, how access to it is granted, which plan shapes it sells, and whether it supports early admission, mirroring and in-place upgrades.
A resource is the core's decision to sell access to a space, a place the installation administers on the platform. resource_kinds lists the kinds of place the connector knows how to gate. A connector that gates nothing (a pure payment or messaging connector) leaves the list out; a connector that declares access_control must list at least one kind (access.declares_kinds).
"resource_kinds": [
{
"kind": "channel",
"label": "Channel",
"portal_label": "Channel",
"icon": "megaphone",
"grant_mode": "bearer_link",
"supports_early_admission_hold": true,
"mirrorable": true,
"plan_kinds": ["one_time", "recurring", "pass", "pass_series"]
}
]The fields
| Field | Required | Meaning |
|---|---|---|
kind | ✅ | ^[a-z][a-z0-9_-]*$, unique within the connector. Stored on a resource as <key>:<kind> (telegram:channel), the value the API returns as kind and the ResourceKind value object carries. |
label | ✅ | What creators see: in the "Link a resource" picker, on resource rows, in plan editors. A translation key. |
portal_label | ✅ | What members see on the portal and in confirmations. Often the same word; sometimes friendlier ("Private room" for a room). A translation key. |
icon | ✅ | A Heroicons outline name (megaphone, user-group, users, home, clipboard-document-check), drawn beside the kind wherever the label appears. |
grant_mode | ✅ | How the connector gives a member access to a place of this kind; one of the four modes below. |
supports_early_admission_hold | Whether a dated grant (a pass whose window has not opened) can be issued early and held at the door until the window opens. Default false. Requires the early_admission_hold capability. | |
mirrorable | Whether a standby place of this kind can receive a live copy of every post from the place it stands by for (RecoverySupport::mirror()). Default false. Requires recovery_mirror. | |
upgrades_from | Another kind of the same connector that a place of this kind can be an in-place upgrade of. A resource sold as that kind may move onto a place of this kind in a recovery swap or take one as its standby; any other pair of kinds is refused, because the plans that sell the resource promised one kind of place. Must name a kind declared in the same file. | |
plan_kinds | The shapes of plan a place of this kind can be sold under: one_time, recurring, pass, pass_series. Every shape when omitted. The directory renders these as the four-row checklist on the kind's card, so a connector whose places cannot be left by force says so honestly rather than leaving creators to find out at the first cancellation. |
What a kind can sell
plan_kinds is a promise to creators, and the loader holds it to three rules: the list is never empty (a kind that sells nothing is not a resource kind), a pass_series needs pass (a series bundles other plans' passes), and a kind with supports_early_admission_hold: true sells pass (the hold exists for a pass whose window has not opened). The order in the file does not matter; the SDK spells the list in the enum's order, one-time, recurring, pass, series, so two manifests never list the same set differently.
| Value | The creator can sell | Declare it when |
|---|---|---|
one_time | One payment for access that never renews, a lifetime membership included. | Almost always; the only thing it needs is a way in. |
recurring | Access billed each cycle and ended when the member stops paying. | The connector can revoke access on its own, so a lapsed member is really removed. |
pass | A ticket to one dated access window. | Access can be given and taken at a set time, whether or not the connector can hold a member at the door first. |
pass_series | One payment for a curated slate of other plans' passes. | The kind sells pass. |
The four grant modes
The mode decides what the core stores as a grant's reference, what AccessController::grant() is expected to do, and what a member is shown.
| Mode | The grant is | Reference stored | The member sees |
|---|---|---|---|
bearer_link | A personal, single-use link the connector mints; whoever presents it is let in. Telegram's invite link that creates a join request the bot approves. | The link itself, unique per connector. | The link, in the confirmation message and on the portal's membership card ("Join …"). |
membership | The installation adds the account to the place directly. A forum's private board, a chat the bot can add people to. | The connector's handle, or null. | "You now have access", and the connector's grant_action slot if it fills one. |
role | The installation gives the account a role that unlocks the place. Discord's role gate. | guild:role:user, repeatable across grants. | The role appears on the platform; nothing to tap. |
creator_task | Nothing the platform can do: the creator must act by hand (add the person, ship a perk). The core opens a creator task and tells the member their organiser will act. | Null until the task is completed. | "Your organiser will add you"; the grant turns granted when the creator completes the task. |
A bearer_link reference is unique per connector (a join request finds the purchase behind the link it arrived on through Core\Grants::findByReference()); a role reference may legitimately repeat across two concurrent subscriptions of the same member, and the core's access policy treats that as legal.
Early admission
A pass sells access to a place for a dated window. When the window has not opened yet, the core asks the connector to grant() with opensAt set. A kind with supports_early_admission_hold: true (and the early_admission_hold capability) may pre-issue the grant and answer held: true, so the member can already present the link and wait at the door; when the window opens the core calls admit() and the connector lets them in. A kind without it is granted when the window opens, and the plan editor's copy about early admission is hidden for plans built on it.
The Telegram kinds
"resource_kinds": [
{"kind": "channel", "label": "Channel", "portal_label": "Channel", "icon": "megaphone", "grant_mode": "bearer_link", "supports_early_admission_hold": true, "mirrorable": true, "plan_kinds": ["one_time", "recurring", "pass", "pass_series"]},
{"kind": "group", "label": "Group", "portal_label": "Group", "icon": "user-group", "grant_mode": "bearer_link", "supports_early_admission_hold": true, "plan_kinds": ["one_time", "recurring", "pass", "pass_series"]},
{"kind": "supergroup", "label": "Supergroup", "portal_label": "Supergroup", "icon": "users", "grant_mode": "bearer_link", "supports_early_admission_hold": true, "upgrades_from": "group", "plan_kinds": ["one_time", "recurring", "pass", "pass_series"]}
]Telegram cannot add anyone to anything, so every kind is a bearer_link; a join request waits at the door, so every kind supports early admission and every kind sells all four plan shapes; only a channel is mirrorable, because a channel is a feed of posts the bot can copy and a group is a conversation it cannot. A supergroup upgrades_from a group because Telegram converts a group into a supergroup in place, so a resource sold as a group may be swapped onto the supergroup it became.
What the kit checks
manifest.resource_kinds: every entry has a non-empty label, portal_label and icon. access.declares_kinds: a connector that declares access_control lists at least one kind. The loader refuses a kind outside its pattern, a duplicate kind, a grant_mode outside the four, a plan_kinds value outside the four or a list that breaks the three rules above, and an upgrades_from naming a kind the file does not declare.
How is this guide?
install
How a creator connects the connector — the install mode, the scopes, and the field specification the install and settings forms are declared with.
capabilities
What a connector can do — the fourteen capabilities, the port each one binds, the group it is shelved under, which are official-only, and which a creator may switch off per installation.