Skip to main content
Entitlements answer “what does this customer get?” — for both dollar features and credit-backed features. Read them with hooks when you need the raw value to drive logic, labels, or meters; wrap UI in a gate component when you just want to render conditionally. Both resolve from the bulk entitlement cache populated by useCustomerone HTTP call serves every hook and gate on the page, so you can scatter as many as you like without extra round-trips. Every hook returns isLoading: boolean and error: Error | null — see Introduction. The typed hooks and gates also share two conventions, so they’re stated once here rather than repeated per feature type:
  • fallback — a type-safe default value returned while loading, on error, or when the customer has no entitlement for the slug. When a fallback value is returned instead of a live one, the hook’s isFallback is true.
  • Gate slots — every gate renders children on access, noAccessComponent (default null) on denial, and loadingComponent (default null) while resolving. The typed gates take fallback for the same purpose as the hooks. (FeatureGate is the exception — it uses fallback for the denied slot; see below.)
FeatureGate takes featureSlug. Every typed gate (BooleanGate, MeteredGate, CreditGate, ConfigGate, EnumGate) takes slug.

The six gates

ComponentPropUse when
FeatureGatefeatureSlugGeneric gate for any feature type; gives you onDenied(reason) and a single fallback.
BooleanGateslugAn on/off feature flag (e.g. SSO, audit log).
MeteredGateslug, requestedUsageA metered feature with a quota; probe remaining capacity before an action.
CreditGateslug, requiredBalanceA credit-backed feature; require a minimum wallet balance.
ConfigGateslugA config-valued feature; render with the resolved value via a render-prop.
EnumGateslug, requestedValuesAn enum feature; require specific values to be in the allowed set.

useEntitlement

Reads one feature’s entitlement by slug and returns the raw, type-discriminated value. By default it serves the read from the bulk useCustomer cache. Pass requestedUsage (metered) or requestedValues (enum) to force a live POST /v1/customers/:id/check capacity probe instead. Prefer one of the typed wrappers below when you know the feature type. Parameters
featureSlug
string
required
The feature’s stable slug.
options.requestedUsage
number
Metered features: ask “would I have access if I consumed N units now?”. Forces a live /check capacity probe rather than a cache read.
options.requestedValues
string[]
Enum features: check whether these requested values are in the allowed set. Also forces a live /check.
options.fallback
EntitlementValue
Value used while loading, on error, or on a cache miss. When returned, isFallback is true.
Returns
  • entitlementEntitlementValue | null — the resolved value. Narrow on entitlement.type ('boolean' | 'metered' | 'credit' | 'config' | 'enum') to read the type-specific fields — e.g. remaining for metered, creditBalance for credit, value for config, enumValues for enum.
  • isFallbackbooleantrue when entitlement came from options.fallback rather than a live read.
import { useEntitlement } from '@unitpay/react';

export default function SeatsLabel() {
  const { entitlement, isLoading } = useEntitlement('seats-ftr');
  if (isLoading) return <div>Loading…</div>;
  if (entitlement?.type === 'metered') {
    return <div>{entitlement.remaining} seats left</div>;
  }
  return null;
}

useEntitlements

Reads multiple features at once. By default it adds zero HTTP — it derives from useCustomer().entitlements, optionally filtered to the slugs you pass — and returns both the keyed map and a slug-sorted list for feature matrices. Pass { fresh: true } to force a live batch check (e.g. right after a mutation). Parameters
slugs
string[]
Optional filter. When provided, only these slugs are returned; omit it to get the customer’s entire entitlement map.
options.fresh
boolean
When true, skip the local cache and call POST /v1/customers/:id/check/batch for slugs. Requires a non-empty slugs argument (the server caps a batch at 50).
options.fallback
Record<string, EntitlementValue>
Per-slug values used while loading, on error, or for slugs the customer doesn’t have.
Returns
  • entitlementsRecord<featureSlug, EntitlementValue> — the filtered map, each value discriminated by type.
  • listArray<{ featureSlug } & EntitlementValue> — the same data sorted alphabetically by featureSlug. Ideal for iterating a table.
import { useEntitlements } from '@unitpay/react';

export default function PlanTable() {
  const { list, error } = useEntitlements();
  if (error) return <div>Couldn’t load features.</div>;
  return (
    <ul>
      {list.map((e) => (
        <li key={e.featureSlug}>
          {e.featureSlug}: {e.access ? 'on' : 'off'}
        </li>
      ))}
    </ul>
  );
}

FeatureGate

The generic, type-agnostic gate: renders its children only when the customer has access to a feature, regardless of the feature’s type. It resolves through useEntitlement (served from the useCustomer cache) and exposes an onDenied callback that fires once with a typed reason when access is refused. Props
featureSlug
string
required
The feature’s stable slug.
children
ReactNode
required
Rendered when access is granted.
fallback
ReactNode
default:"null"
Rendered when access is denied.
loading
ReactNode
default:"null"
Rendered while the entitlement is loading.
onDenied
(reason: AccessDeniedReason) => void
Called once when access is denied (and again only after access is regained and lost again). The reason is one of: no_entitlement, no_active_subscription, usage_exceeded, credit_exhausted, not_loaded.
requestedUsage
number
Metered features: capacity probe — would access be granted after consuming this many units? Forces a live POST /v1/customers/:id/check.
fallbackEntitlement
EntitlementValue
Type-safe entitlement default used while loading or on a cache miss. For ergonomics, prefer the typed gates and their fallback prop.
import { FeatureGate } from '@unitpay/react';

export default function Example() {
  return (
    <FeatureGate featureSlug="sso" fallback={<UpgradePrompt />}>
      <SSOSettings />
    </FeatureGate>
  );
}
Probe metered capacity before showing an action:
<FeatureGate
  featureSlug="api-calls"
  requestedUsage={1}
  fallback={<OutOfQuota />}
  onDenied={(reason) => track('gate_denied', { reason })}
>
  <RunQueryButton />
</FeatureGate>

Boolean features

An on/off feature flag — SSO, audit log, priority support. useBooleanEntitlement(slug, options?) returns a flat access booleantrue when the customer has the feature, false otherwise (or for any non-boolean / missing entitlement). Its options.fallback is { access: boolean }. BooleanGate renders children only when access is true.
import { useBooleanEntitlement } from '@unitpay/react';

export default function SsoSettings() {
  const { access, isLoading } = useBooleanEntitlement('sso-ftr', {
    fallback: { access: false },
  });
  if (isLoading) return <div>Loading…</div>;
  if (!access) return <div>SSO is available on the Enterprise plan.</div>;
  return <SsoConfigPanel />;
}

Metered features

A metered feature with a per-period quota — API calls, seats, exports. useMeteredEntitlement(slug, options?) returns the full meter picture: access, remaining (quota left this period — granted − usage, the canonical “what’s left”), usage, limit (granted allowance), percentage (usage / limit × 100, 0 when unlimited), isUnlimited (when true, ignore limit/remaining/percentage), and nextResetAt (ISO timestamp of the next reset, or null). Pass options.requestedUsage to force a live /check capacity probe; options.fallback is { access: boolean; remaining?: number; isUnlimited?: boolean; limit?: number; usage?: number }. MeteredGate renders children only when access exists; pass requestedUsage so it succeeds only when that many units could still be consumed this period.
import { useMeteredEntitlement } from '@unitpay/react';

export default function ApiQuota() {
  const { access, usage, limit, remaining, percentage, nextResetAt, isUnlimited, isLoading, error } =
    useMeteredEntitlement('api-calls-ftr');

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load usage.</div>;
  if (!access) return <div>Upgrade to call the API.</div>;
  if (isUnlimited) return <div>Unlimited API calls</div>;

  return (
    <div>
      <progress value={percentage} max={100} />
      <p>{usage} / {limit} used · {remaining} left</p>
      {nextResetAt && <p>Resets {new Date(nextResetAt).toLocaleDateString()}</p>}
    </div>
  );
}
Passing requestedUsage to either the hook or the gate forces a server-side check — “do we have headroom for N more units?” — instead of reading the static remaining from cache:
import { useMeteredEntitlement } from '@unitpay/react';

export default function BulkImportButton({ rows }: { rows: number }) {
  const { access, isLoading } = useMeteredEntitlement('api-calls-ftr', {
    requestedUsage: rows,
  });
  return (
    <button disabled={isLoading || !access}>
      {access ? `Import ${rows} rows` : 'Not enough quota'}
    </button>
  );
}

Credit features

A credit-backed feature drawing from a wallet — AI credits, generation credits, prepaid balance. useCreditEntitlement(slug, options?) returns access and creditBalance (the wallet balance backing this feature). Render creditBalance with formatCredits(n) for unit denominations or as money for fiat denominations — never mix the two. Its options.fallback is { access: boolean; creditBalance: number }. CreditGate renders children only when access exists and the balance is at least requiredBalance (default 1).
import { useCreditEntitlement } from '@unitpay/react';

export default function CreditBalance() {
  const { access, creditBalance, isLoading, error } = useCreditEntitlement('ai-credits-ftr', {
    fallback: { access: false, creditBalance: 0 },
  });

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load credits.</div>;
  if (!access) return <div>No credit wallet on your plan.</div>;

  return <div>{creditBalance} credits remaining</div>;
}
CreditGate’s extra prop:
requiredBalance
number
default:"1"
Minimum credit balance required to render children.

Config features

A config-valued feature whose entitlement carries a tunable value — “max file size”, “support tier” — a plan sets per tier. useConfigEntitlement(slug, options?) returns access and value (a number or string, or null when there’s no config entitlement). Its options.fallback is { access: boolean; value: number | string }. ConfigGate renders a render-prop children — a function (value: number | string) => ReactNode called with the resolved value when access is granted; noAccessComponent renders when access is denied or the value is null.
import { useConfigEntitlement } from '@unitpay/react';

export default function UploadLimit() {
  const { access, value, isLoading, error } = useConfigEntitlement('max-upload-mb-ftr', {
    fallback: { access: false, value: 25 },
  });

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load your plan limits.</div>;
  if (!access) return <div>Uploads aren’t included in your plan.</div>;

  return <div>Max upload size: {value} MB</div>;
}

Enum features

An enum feature whose entitlement is a set of allowed string values — allowed regions, export formats, model tiers. useEnumEntitlement(slug, options?) returns access and enumValues (the array of allowed strings, empty for any non-enum / missing entitlement). Pass options.requestedValues to probe whether specific values are permitted (forces a live /check) — access then reflects whether those values are in the allowed set. Its options.fallback is { access: boolean; enumValues: string[] }. EnumGate renders children only when access exists; pass requestedValues to require specific values to be in the allowed set.
import { useEnumEntitlement } from '@unitpay/react';

export default function RegionPicker() {
  const { access, enumValues, isLoading, error } = useEnumEntitlement('regions-ftr', {
    fallback: { access: false, enumValues: [] },
  });

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load regions.</div>;
  if (!access) return <div>Region selection isn’t in your plan.</div>;

  return (
    <select>
      {enumValues.map((r) => (
        <option key={r} value={r}>{r}</option>
      ))}
    </select>
  );
}

See also

Customer

useCustomer loads the entitlement cache these hooks and gates read from, plus a synchronous check().

Subscriptions

The subscriptions that grant these entitlements.