Capabilities & entitlements
@Capability, @Entitlement, and capabilityGrant — bundling access and granting it on plans.
@Capability, @Entitlement, and capabilityGrant — bundling access and granting it on plans.
A @Capability is a named bundle of access — the features, policies, and
other capabilities a subscriber gets when they hold it. Plans grant
capabilities (optionally with per-capability resource limits) via capabilityGrant. An
@Entitlement is a reusable, product-level grouping of capabilities, feature gates, limits,
and meters that the dashboard and portals read.
import { Product, Resource, Capability, Feature, Plan, capabilityGrant } from "@farthershore/product";
@Product({ name: "croncloud", origin: "https://api.example.com" })
export default class CronCloud {
@Resource("cron_jobs", { display: "Cron jobs", countSource: "action_inferred" })
cronJobs!: unknown;
@Capability("managed-cron", {
title: "Managed Cron Jobs",
includesFeatures: ["cron-jobs"],
})
managedCron!: unknown;
@Feature("cron-jobs", { plans: ["starter"], routes: { "GET /v1/cron-jobs": {} } })
cronJobsFeature!: unknown;
@Plan("starter", {
name: "Starter",
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 10 } })],
limits: { requests: { rate: 600, interval: "minute" } },
})
starter!: unknown;
}
@Capability(key, options) declares a capability. The key is the string plans and pages
reference. includesFeatures names feature keys this capability unlocks;
the named features must exist or the build fails (capability "…" depends on missing feature "…").
| Option | Type | Meaning |
|---|---|---|
title | string | Human label for the capability. |
description | string | Longer description. |
includesFeatures | string[] | Feature keys this capability unlocks. |
includesPolicies | string[] | Policy keys this capability includes. |
includesCapabilities | string[] | Other capability keys this one composes. |
mutationClass | "runtime" | "contractual" | Mutation classification. |
@Capability("managed-cron", {
title: "Managed Cron Jobs",
includesFeatures: ["cron-jobs"],
})
managedCron!: unknown;
compiles to a capability layer:
{ "capability": "managed-cron", "includes_features": ["cron-jobs"] }
References may be declared in any order — a capability can name a feature declared later in the class. Only truly missing references fail the build.
A plan grants a capability through capabilityGrant(key, { limits }) in its grants[]. The
optional limits set per-capability resource caps (numeric or boolean) — this is how you cap
a counted resource like cron_jobs on a specific plan.
import { capabilityGrant } from "@farthershore/product";
@Plan("starter", {
name: "Starter",
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 10 } })],
limits: { requests: { rate: 600, interval: "minute" } },
})
starter!: unknown;
@Plan("pro", {
name: "Pro",
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 100 } })],
limits: { requests: { rate: 6000, interval: "minute" } },
})
pro!: unknown;
On the starter plan this folds into:
{ "key": "starter", "capabilities": ["managed-cron"], "capability_limits": { "cron_jobs": 10 } }
capabilities is a first-class array on the plan; the limits land in capability_limits.
For a capability with no per-capability limits, two spellings are equivalent: a bare
grants: [capabilityGrant("managed-cron")], or the shorthand capabilities: ["managed-cron"]
on the plan. Use capabilityGrant with limits only when you need the resource caps.
@Entitlement(key, options) groups capabilities, feature gates, limits, and meters into one
reusable, product-level bundle. Unlike a capability (which a plan grants), an entitlement is
descriptive metadata the platform surfaces — it composes existing declarations into a named
access tier.
| Option | Type | Meaning |
|---|---|---|
description | string | Human description. |
capabilities | string[] | Capability keys in this entitlement. |
featureGates | Record<string, boolean> | Feature-gate flags. |
limits | PlanLimit[] | Rate limits (the IR limits[] shape: { dimension, window, capacity }). |
meters | string[] | Meter keys in this entitlement. |
@Meter("workflow_runs", { unit: "run" })
workflowRuns!: unknown;
@Capability("premium_tools")
premium!: unknown;
@Entitlement("premium_access", {
capabilities: ["premium_tools"],
featureGates: { premium_tools: true },
meters: ["workflow_runs"],
limits: [
{ dimension: "workflow_runs", window: { type: "named", name: "month" }, capacity: 1000 },
],
})
premiumAccess!: unknown;
compiles to product.entitlements:
{ "key": "premium_access",
"capabilities": ["premium_tools"],
"featureGates": { "premium_tools": true },
"limits": [{ "dimension": "workflow_runs", "window": { "type": "named", "name": "month" }, "capacity": 1000 }],
"meters": ["workflow_runs"] }
The limits on an @Entitlement use the raw IR limit shape ({ dimension, window, capacity }),
not the ergonomic { rate, interval } shape that @Plan limits use. They are
different surfaces: a plan's limits are authored ergonomically and normalized; an entitlement
carries the IR shape verbatim.
@Resource("cron_jobs") ← a counted thing
▲ capped by
@Capability("managed-cron") ← bundles features, granted on plans
includesFeatures: ["cron-jobs"]
▲ granted by
@Plan(...).grants:
capabilityGrant("managed-cron", { limits: { cron_jobs: 10 } })
A subscriber on the starter plan holds managed-cron, which unlocks the cron-jobs feature
and its routes, and may create up to 10 cron_jobs. See features & routes
for the actions that maintain the count and plans & pricing for the full plan
shape.