# Feature flags (/docs/features/feature-flags)

> Turn product features on and off per organization without a deploy, from the platform or each organization, and gate them in the UI and in authorization.



A new feature rarely reaches every customer on the same day. You want to try it with a design partner first, roll it
out to a quarter of your customers, let each organization opt in to a beta, and switch it off everywhere in seconds
when something goes wrong, all without a deploy. Feature flags do that, and because they live in Better IAM next to
your organizations, a flag can decide what the UI shows and what authorization allows with the same switch.

Every flag is a boolean, and flags work at two levels:

* **Platform flags** are defined on the root tenant by root administrators (or anyone the root tenant grants
  `iam:features:manage`). They reach every organization and project.
* **Organization flags** are defined by an organization (or a project) for its own subtree, for example to roll a
  feature out to some of its projects, or to let each project choose.

Flags are per tenant. To entitle individual people, combine a flag with license seats (see
[Licenses](/docs/features/licenses#feature-keys-in-your-application)).

## Setup [#setup]

Feature flags are always on: the `features` API group and `iam.features` exist on every instance, and there is no
option to set.

      ### Migrate [#migrate]

    Flags, targets, and overrides are stored in the shared `iam_records` table, which the ordinary migration creates.

    ```bash
    npx better-iam migrate --config better-iam.config.mjs
    ```
  
      ### Create the client and the provider [#create-the-client-and-the-provider]

    Browsers read flags through the typed client, and React components through hooks rendered inside `IamProvider`.

    ```tsx title="app/providers.tsx"
    import { createIamClient } from 'better-iam/client';
    import { IamProvider } from 'better-iam/react';
    import type { ReactNode } from 'react';
    import type { iam } from '../iam';

    const client = createIamClient<typeof iam>({ baseURL: 'https://app.acme.test' });

    export function Providers({ children }: { children: ReactNode }) {
      return <IamProvider client={client}>{children}</IamProvider>;
    }
    ```
  
## How a value is decided [#how-a-value-is-decided]

A flag's value for a tenant comes from the first of four rules that applies:

<Mermaid
  chart="flowchart TD
  K{&#x22;Kill switch on?&#x22;} -->|yes| OFF[&#x22;false&#x22;]
  K -->|no| C{&#x22;A target or override between this tenant<br/>and the tenant that defines the flag?&#x22;}
  C -->|&#x22;the closest one decides&#x22;| V[&#x22;its value&#x22;]
  C -->|none| R{&#x22;Rollout percentage set?&#x22;}
  R -->|no| D[&#x22;defaultValue&#x22;]
  R -->|yes| B{&#x22;Tenant's branch inside it?&#x22;}
  B -->|yes| ON[&#x22;true&#x22;]
  B -->|no| RO[&#x22;false&#x22;]"
/>

1. **Kill switch.** `killSwitch: true` turns the flag off everywhere. Targets and overrides stay stored and apply
   again when the switch is lifted.
2. **The closest target or override.** Walking from the tenant up to (but not including) the tenant that defines the
   flag, the first tenant with a value decides:
   * A **target** is a value the flag's managers pin for a tenant below them with `features.setTarget`, such as "on
     for Acme until the end of the trial". It applies to that tenant and everything below it, unless a closer target
     or override decides, and it can lapse (`expiresAt`).
   * An **override** is a tenant's own choice with `features.setOverride`, allowed only for flags defined with
     `tenantOverridable: true`. On the same tenant, the tenant's override beats an unlocked target. Overrides are
     refused for internal flags and on the tenant that defines the flag (change the flag itself there with
     `features.update`). Withdrawing a choice with `value: null` is always allowed, even while locked.
   * A **locked** target (`locked: true`) silences overrides at its tenant and below, so an organization cannot undo
     it. A closer target from the flag's own managers still wins over it.
3. **Rollout.** `rolloutPercentage` (0 to 100) turns an off-by-default flag on for a stable share of the branches
   directly below the defining tenant, and off for the rest. For a platform flag those branches are organizations, so
   an organization's projects always land on the same side as the organization. Raising the percentage only adds
   tenants; nobody who had the feature loses it. `rolloutBucket(key, tenantId)` from `@better-iam/server` shows where
   a tenant falls.
4. **Default.** When no rollout is set, `defaultValue`.

A flag with a rollout must default to off (`defaultValue: false`), because the rollout is the share that is turned
on. Each evaluation reports its `reason`: `KILL_SWITCH`, `TARGET`, `OVERRIDE`, `ROLLOUT`, `DEFAULT`, or `UNKNOWN` (no
flag with that key reaches the tenant).

**Keys belong to the tenant closest to the root.** A tenant cannot define a key an ancestor already defines, and a
platform flag created later takes precedence over an organization flag with the same key (`features.list` shows the
organization's flag as `shadowed`). So an organization can never switch a platform-gated feature on for itself. Keys
start with a lowercase letter and join lowercase letters and digits with single `-`, `_`, or `.` characters
(`new-billing`, `reports.v2`; not `1st`, `a--b`, or `trailing-`), up to 64 characters, with at most 200 flags per
tenant.

## Usage [#usage]

### Define a flag [#define-a-flag]

`features.create` (`iam:features:manage`) defines a flag in the tenant you name; a platform flag is defined on the root
tenant. `features.update` changes settings (omitted fields are kept, and `null` clears `description` or
`rolloutPercentage`), and `features.delete` removes the flag with all its targets and overrides, after which code that
asks for it gets `false`.

```ts
const root = { token: rootSessionToken };

// A platform flag that organizations may turn on for themselves.
await iam.api.features.create(root, {
  tenantId: rootTenantId,
  key: 'new-billing',
  description: 'The redesigned billing pages',
  tenantOverridable: true,
});

// A gradual rollout to a quarter of all organizations.
await iam.api.features.update(root, { tenantId: rootTenantId, key: 'reports-v2', rolloutPercentage: 25 });
```

**Route:** `POST /api/iam/features/create` (also `/features/update`, `/features/delete`)

### Target a tenant [#target-a-tenant]

`features.setTarget` (`iam:features:manage` in the defining tenant) pins a value for a tenant below the defining one;
`value: null` removes it. `features.listTargets` (`iam:features:read` in the defining tenant) lists every target and
override below the defining tenant, with `active` for those that count right now.

```ts
// A two-week trial for one organization.
await iam.api.features.setTarget(root, {
  tenantId: rootTenantId,
  key: 'fast-search',
  targetTenantId: acmeId,
  value: true,
  expiresAt: Date.now() + 14 * 86_400_000,
  note: 'Design partner trial',
});

const targets = await iam.api.features.listTargets(root, { tenantId: rootTenantId, key: 'fast-search' });
```

**Route:** `POST /api/iam/features/setTarget` (also `/features/listTargets`)

Target notes are for the flag's managers. An organization sees that a target exists, its value, whether it is locked,
and when it ends, but never the note.

### Let an organization choose [#let-an-organization-choose]

`features.setOverride` (`iam:features:override`, authorized in the tenant making the choice) records a tenant's own
value for a flag defined with `tenantOverridable: true`. It answers `FEATURE_LOCKED` when the flag does not allow
overrides or a locked target covers the tenant.

```ts
// Acme's administrators opt in to the new billing pages.
await iam.api.features.setOverride(acmeAdmin, { tenantId: acmeId, key: 'new-billing', value: true });
// And return to the default later.
await iam.api.features.setOverride(acmeAdmin, { tenantId: acmeId, key: 'new-billing', value: null });
```

**Route:** `POST /api/iam/features/setOverride`

### Turn a feature off everywhere [#turn-a-feature-off-everywhere]

The kill switch is a flag setting, so lifting it restores every target and override.

```ts
await iam.api.features.update(root, { tenantId: rootTenantId, key: 'fast-search', killSwitch: true });
```

**Route:** `POST /api/iam/features/update`

### List the flags that reach a tenant [#list-the-flags-that-reach-a-tenant]

`features.list` (`iam:features:read`) returns the flags that reach the tenant, each with its evaluation, the tenant's
override, and its target, plus the tenant's own flags that an ancestor `shadowed`.

```ts
const { flags, shadowed } = await iam.api.features.list(acmeAdmin, { tenantId: acmeId });
```

**Route:** `POST /api/iam/features/list`

### Read flags in your application [#read-flags-in-your-application]

On the server, deployment code reads flags without a credential, internal flags included:

```ts
import { iam } from './iam';

if (await iam.features.isEnabled(tenantId, 'new-billing')) showNewBilling();
const values = await iam.features.values(tenantId); // { 'new-billing': true, ... }
const [detail] = await iam.features.evaluate(tenantId, { keys: ['new-billing'] });
// { key, value, reason: 'OVERRIDE', scope: 'platform', definedBy, decidedBy, locked, overridable }
```

From a browser or another service, `features.evaluate` needs only a credential of the tenant: a user session, an API
key, a role session, or a session token whose tenant it is (root administrators may evaluate any tenant). It is not
audited and leaves out ancestors' internal flags:

  **Server:**

    ```ts
    const { flags } = await iam.api.features.evaluate({ headers: request.headers }, { tenantId });
    ```
  
  **Client:**

    ```ts
    import { client } from './lib/iam-client';

    const { flags } = await client.features.evaluate({ tenantId, keys: ['new-billing'] });
    ```
  
  **React:**

    ```tsx
    import { useFeatureFlag, useFeatureFlags } from 'better-iam/react';

    function Billing({ tenantId }: { tenantId: string }) {
      const { value } = useFeatureFlag({ tenantId, key: 'new-billing' });
      return value ? <NewBilling /> : <ClassicBilling />;
    }
    function Toolbar({ tenantId }: { tenantId: string }) {
      const features = useFeatureFlags({ tenantId });
      return features.isEnabled('fast-search') ? <FastSearch /> : null;
    }
    ```
  
**Route:** `POST /api/iam/features/evaluate`

The hooks return `false` while loading, when signed out, and for unknown keys. Hiding a button is not enforcement, so
gate the server side as well, in code or in a policy.

### Internal flags [#internal-flags]

**Internal flags** (`internal: true`) are for trusted server code and policies only. They are hidden from
`features.evaluate` and from the `features.list` of tenants below the defining one (root administrators still see
them). Targets can reach them, but tenants cannot override them, so `tenantOverridable` is refused for them.

Each flag is the resource `iam/features/{key}` (`features.list` uses `iam/features`), so a policy can delegate one
flag: for example `iam:features:override` on `iam/features/new-*` for a product team. Every change is audited
(`feature:create`, `feature:update` with the settings before and after, `feature:delete`, `feature:target`,
`feature:override`) and reaches webhooks like any audit event.

## Flags in policies [#flags-in-policies]

Decisions see the keys of every flag that is on for the decision's tenant, internal flags included, as the list
`tenant.features`. A policy can then allow an action only where a feature is on:

```json title="Exports only where the exports feature is on"
{
  "effect": "allow",
  "actions": ["documents:export"],
  "resources": ["*"],
  "conditions": { "ArrayContains": { "tenant.features": "exports" } }
}
```

The server reads flags only for decisions whose documents name `tenant.features`, so policies that do not use flags
pay nothing. Applications cannot supply the key through `resolveContext` or plugins, because the server removes it.
Policy lint treats it as a list, so test it with `ArrayContains`, not string operators.
[`policies.test`](/docs/authorization/reviews) fills it with the tenant's current flags when the candidate
document names it, and a `tenant.features` value you pass in the test's `context` takes precedence.

## Options [#options]

There is no server option for feature flags. Each flag carries these settings (`features.create`, `features.update`):

| Option              | Type      | Default  | Description                                                           |
| ------------------- | --------- | -------- | --------------------------------------------------------------------- |
| `key`               | `string`  | required | Lowercase flag key, up to 64 characters. Never changes.               |
| `description`       | `string`  | none     | Up to 512 characters; `null` clears it on update.                     |
| `defaultValue`      | `boolean` | `false`  | The value when no target, override, or rollout decides.               |
| `rolloutPercentage` | `number`  | none     | 0 to 100; requires `defaultValue: false`; `null` clears it on update. |
| `tenantOverridable` | `boolean` | `false`  | Tenants below may choose their own value. Refused for internal flags. |
| `killSwitch`        | `boolean` | `false`  | Off everywhere, whatever targets and overrides say.                   |
| `internal`          | `boolean` | `false`  | Only server code and policies see the flag.                           |

Targets (`features.setTarget`) take `targetTenantId`, `value` (`null` removes the target), `locked`, `expiresAt`, and
`note` (up to 512 characters).

## Permissions [#permissions]

| Action                  | Where                                                | Methods                                   |
| ----------------------- | ---------------------------------------------------- | ----------------------------------------- |
| `iam:features:manage`   | The defining tenant                                  | `create`, `update`, `delete`, `setTarget` |
| `iam:features:read`     | The tenant read (`listTargets`: the defining tenant) | `list`, `listTargets`                     |
| `iam:features:override` | The tenant making the choice                         | `setOverride`                             |
| None                    | Any credential of the tenant                         | `evaluate`                                |

## Schema [#schema]

Records live in the `iam_records` table under two tenant-scoped collections. A target or override lives in the tenant it
applies to, so purging that tenant removes it; deleting the flag removes all of them.

| Collection       | Holds                                                   | Key fields                                                                                                                                                   |
| ---------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `featureFlags`   | A flag the tenant defines                               | `key`, `description`, `defaultValue`, `rolloutPercentage`, `tenantOverridable`, `killSwitch`, `internal`, `createdAt`, `createdBy`, `updatedAt`, `updatedBy` |
| `featureTargets` | A target or override for one tenant and its descendants | `flagId`, `key`, `definerId` (the defining tenant), `source` (`target` or `override`), `value`, `locked`, `expiresAt`, `note`, `setAt`, `setBy`              |

## Errors [#errors]

| Code                                                              | Status | When                                                                                                                                                                                                          |
| ----------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`FEATURE_LOCKED`](/docs/reference/errors#feature_locked)         | 409    | `setOverride` on a flag that does not let tenants choose, or under a locked target                                                                                                                            |
| [`CONFLICT`](/docs/reference/errors#conflict)                     | 409    | The key exists in the tenant, or an ancestor (such as the platform) already defines it                                                                                                                        |
| [`LIMIT_EXCEEDED`](/docs/reference/errors#limit_exceeded)         | 409    | More than 200 flags in a tenant                                                                                                                                                                               |
| [`NOT_FOUND`](/docs/reference/errors#not_found)                   | 404    | An unknown flag, or an internal flag of another tenant                                                                                                                                                        |
| [`ACCESS_DENIED`](/docs/reference/errors#access_denied)           | 403    | Missing permission, or `evaluate` with a credential of another tenant                                                                                                                                         |
| [`INVALID_INPUT`](/docs/reference/errors#invalid_input)           | 400    | A malformed key, a rollout with `defaultValue: true`, an override on the defining tenant, a target for the defining tenant itself or a tenant outside its subtree, or `tenantOverridable` on an internal flag |
| [`INVALID_TRANSITION`](/docs/reference/errors#invalid_transition) | 409    | Changing flags of a deleted tenant                                                                                                                                                                            |

## In the console [#in-the-console]

* **Administration → Feature flags** (root): platform flags with their settings and kill switch, every target and
  organization choice, a form to target an organization or project (optionally locked or lapsing), and new flags.
* **Organization → Features**: the platform's flags as they apply to the organization, with **Turn on**,
  **Turn off**, and **Use default** where the platform allows a choice, plus the organization's own flags with
  per-project targets.

## Next steps [#next-steps]

  - [Features API](/docs/reference/api/features): Every method, permission, and error of the features group.

  - [Licenses](/docs/features/licenses): Per-person entitlements whose feature keys combine with flags.

  - [Conditions](/docs/authorization/conditions): The operators and context keys policies can test, including lists like tenant.features.
