> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useunitpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Entitlements & gates

> Read a customer's feature entitlements — as typed hooks — and gate UI with declarative components.

import LearnHooksTip from '/snippets/learn-hooks-tip.mdx';

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 [`useCustomer`](/react/customer) — **one HTTP call serves every hook and gate on the page**, so you can scatter as many as you like without extra round-trips.

<LearnHooksTip />

Every hook returns `isLoading: boolean` and `error: Error | null` — see [Introduction](/react/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.)

<Note>
  `FeatureGate` takes **`featureSlug`**. Every typed gate (`BooleanGate`, `MeteredGate`, `CreditGate`, `ConfigGate`, `EnumGate`) takes **`slug`**.
</Note>

## The six gates

| Component     | Prop                      | Use when                                                                                 |
| ------------- | ------------------------- | ---------------------------------------------------------------------------------------- |
| `FeatureGate` | `featureSlug`             | Generic gate for any feature type; gives you `onDenied(reason)` and a single `fallback`. |
| `BooleanGate` | `slug`                    | An on/off feature flag (e.g. SSO, audit log).                                            |
| `MeteredGate` | `slug`, `requestedUsage`  | A metered feature with a quota; probe remaining capacity before an action.               |
| `CreditGate`  | `slug`, `requiredBalance` | A credit-backed feature; require a minimum wallet balance.                               |
| `ConfigGate`  | `slug`                    | A config-valued feature; render with the resolved value via a render-prop.               |
| `EnumGate`    | `slug`, `requestedValues` | An 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**

<ParamField body="featureSlug" type="string" required>
  The feature's stable slug.
</ParamField>

<ParamField body="options.requestedUsage" type="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.
</ParamField>

<ParamField body="options.requestedValues" type="string[]">
  Enum features: check whether these requested values are in the allowed set. Also forces a live `/check`.
</ParamField>

<ParamField body="options.fallback" type="EntitlementValue">
  Value used while loading, on error, or on a cache miss. When returned, `isFallback` is `true`.
</ParamField>

**Returns**

* `entitlement` — `EntitlementValue | 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.
* `isFallback` — `boolean` — `true` when `entitlement` came from `options.fallback` rather than a live read.

```tsx theme={null}
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`](/react/customer), 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**

<ParamField body="slugs" type="string[]">
  Optional filter. When provided, only these slugs are returned; omit it to get the customer's entire entitlement map.
</ParamField>

<ParamField body="options.fresh" type="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).
</ParamField>

<ParamField body="options.fallback" type="Record<string, EntitlementValue>">
  Per-slug values used while loading, on error, or for slugs the customer doesn't have.
</ParamField>

**Returns**

* `entitlements` — `Record<featureSlug, EntitlementValue>` — the filtered map, each value discriminated by `type`.
* `list` — `Array<{ featureSlug } & EntitlementValue>` — the same data sorted alphabetically by `featureSlug`. Ideal for iterating a table.

```tsx theme={null}
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**

<ParamField body="featureSlug" type="string" required>
  The feature's stable slug.
</ParamField>

<ParamField body="children" type="ReactNode" required>
  Rendered when access is granted.
</ParamField>

<ParamField body="fallback" type="ReactNode" default="null">
  Rendered when access is denied.
</ParamField>

<ParamField body="loading" type="ReactNode" default="null">
  Rendered while the entitlement is loading.
</ParamField>

<ParamField body="onDenied" type="(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`.
</ParamField>

<ParamField body="requestedUsage" type="number">
  Metered features: capacity probe — would access be granted after consuming this many units? Forces a live `POST /v1/customers/:id/check`.
</ParamField>

<ParamField body="fallbackEntitlement" type="EntitlementValue">
  Type-safe entitlement default used while loading or on a cache miss. For ergonomics, prefer the typed gates and their `fallback` prop.
</ParamField>

```tsx theme={null}
import { FeatureGate } from '@unitpay/react';

export default function Example() {
  return (
    <FeatureGate featureSlug="sso" fallback={<UpgradePrompt />}>
      <SSOSettings />
    </FeatureGate>
  );
}
```

Probe metered capacity before showing an action:

```tsx theme={null}
<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` boolean** — `true` 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`.

<CodeGroup>
  ```tsx Hook theme={null}
  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 />;
  }
  ```

  ```tsx Gate theme={null}
  import { BooleanGate } from '@unitpay/react';

  export default function Example() {
    return (
      <BooleanGate slug="sso" noAccessComponent={<UpgradePrompt />}>
        <SSOSettings />
      </BooleanGate>
    );
  }
  ```
</CodeGroup>

## 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.

<CodeGroup>
  ```tsx Hook theme={null}
  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>
    );
  }
  ```

  ```tsx Gate theme={null}
  import { MeteredGate } from '@unitpay/react';

  export default function Example() {
    return (
      <MeteredGate slug="api-calls" requestedUsage={1} noAccessComponent={<OutOfQuota />}>
        <RunQueryButton />
      </MeteredGate>
    );
  }
  ```
</CodeGroup>

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:

```tsx theme={null}
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`).

<CodeGroup>
  ```tsx Hook theme={null}
  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>;
  }
  ```

  ```tsx Gate theme={null}
  import { CreditGate } from '@unitpay/react';

  export default function Example() {
    return (
      <CreditGate slug="ai-credits" requiredBalance={10} noAccessComponent={<TopUpPrompt />}>
        <GenerateButton />
      </CreditGate>
    );
  }
  ```
</CodeGroup>

`CreditGate`'s extra prop:

<ParamField body="requiredBalance" type="number" default="1">
  Minimum credit balance required to render children.
</ParamField>

## 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`.

<CodeGroup>
  ```tsx Hook theme={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>;
  }
  ```

  ```tsx Gate theme={null}
  import { ConfigGate } from '@unitpay/react';

  export default function Example() {
    return (
      <ConfigGate slug="support-tier" noAccessComponent={<DefaultSupport />}>
        {(value) => <SupportBanner tier={value} />}
      </ConfigGate>
    );
  }
  ```
</CodeGroup>

## 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.

<CodeGroup>
  ```tsx Hook theme={null}
  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>
    );
  }
  ```

  ```tsx Gate theme={null}
  import { EnumGate } from '@unitpay/react';

  export default function Example() {
    return (
      <EnumGate slug="export-formats" requestedValues={['pdf']} noAccessComponent={<UpgradePrompt />}>
        <ExportPdfButton />
      </EnumGate>
    );
  }
  ```
</CodeGroup>

## See also

<CardGroup cols={2}>
  <Card title="Customer" icon="user" href="/react/customer">
    `useCustomer` loads the entitlement cache these hooks and gates read from, plus a synchronous `check()`.
  </Card>

  <Card title="Subscriptions" icon="repeat" href="/react/subscriptions">
    The subscriptions that grant these entitlements.
  </Card>
</CardGroup>
