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

# Admin workflow

> How environments, flags, targeting rules, and rollouts fit together — and what to do if an SDK key leaks.

This page covers the admin-side flow behind [Overview](/feature-flags/overview) and [Getting started](/feature-flags/getting-started): how environments, flags, targeting rules, and rollouts attach to each other, and what to do when something needs fixing after the fact — a lost SDK key, a rule that isn't matching, a rollout that needs to change.

## How a value gets decided

Before the individual pieces, here's the order they're applied in when a flag is evaluated:

```mermaid theme={null}
flowchart TD
    REQ["Evaluation request\nflag key + context"] --> ENABLED{"Flag enabled\nfor this environment?"}
    ENABLED -- No --> DEFAULT["Return default value"]
    ENABLED -- Yes --> RULES{"Any targeting rule\nmatch the context?"}
    RULES -- Yes --> RULEVAL["Return the matching rule's value"]
    RULES -- No --> ROLLOUT{"Percentage rollout\nconfigured?"}
    ROLLOUT -- Yes --> BUCKET["Bucket by per_user or per_tenant,\nreturn in/out value"]
    ROLLOUT -- No --> BASE["Return the flag's base value"]
```

Targeting rules are evaluated in ascending `priority` order (lowest number = highest priority); the first matching rule wins. If no rule matches, evaluation falls through to the percentage rollout (if configured), then to the flag's base `isEnabled` value.

## Environments

An environment groups runtime settings and produces exactly one SDK key, shown in full only at creation time — the same behavior covered in [Getting started, Step 1](/feature-flags/getting-started).

<Warning>
  If the SDK key is lost or exposed, rotating it invalidates the old one — see [If an SDK key leaks](#if-an-sdk-key-leaks) below before you switch any traffic over.
</Warning>

## Flags

Flag keys are the stable identifiers your application code depends on; names are just the human-readable label shown in the dashboard. Keep the key immutable once code references it — renaming the display name is safe, changing the key is not.

Seeded examples used throughout these docs:

| Key                   | Used as                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `new_dashboard`       | Boolean flag, worked example in [Overview](/feature-flags/overview) and [Getting started](/feature-flags/getting-started) |
| `welcome_message`     | String flag (`type: "string"`), seeded in `seed.ts` — not yet evaluated by SDK                                            |
| `checkout_experiment` | Multivariate flag (`type: "multivariate"`) with a 50% `per_user` rollout on production                                    |

## Targeting rules

A rule matches on attributes from the evaluation context — `country`, `tenant`, `plan`, `userId`, or any custom attribute you pass in.

| Field        | Type              | What it does                                        |
| ------------ | ----------------- | --------------------------------------------------- |
| `priority`   | number            | Evaluation order when a flag has more than one rule |
| `logic`      | `"and"` \| `"or"` | How the listed conditions combine                   |
| `value`      | matches flag type | What to return when this rule matches               |
| `conditions` | array             | The attribute checks below                          |

Each entry in `conditions` uses a discriminated union — exactly one of `valueString`, `valueInt`, or `valueBool` is populated, determined by `valueType`:

| Field         | Type    | What it does                                                    |
| ------------- | ------- | --------------------------------------------------------------- |
| `attribute`   | string  | Context key to check, e.g. `country`, `userId`                  |
| `operator`    | string  | Comparison to apply — `==`, `!=`, `>`, `<`, `>=`, `<=`          |
| `valueType`   | string  | How to interpret the value — `string`, `number`, or `boolean`   |
| `valueString` | string  | The comparison value (only present when `valueType: "string"`)  |
| `valueInt`    | integer | The comparison value (only present when `valueType: "number"`)  |
| `valueBool`   | boolean | The comparison value (only present when `valueType: "boolean"`) |

```json Example theme={null}
{
  "priority": 1,
  "logic": "and",
  "value": true,
  "conditions": [
    { "attribute": "country", "operator": "==", "valueType": "string", "valueString": "US" },
    { "attribute": "plan", "operator": "==", "valueType": "string", "valueString": "premium" }
  ]
}
```

<Tip>
  `logic: "and"` means every condition in the array must match. A rule with two conditions and `"logic": "or"` matches if *either* one does — useful for something like "US-based **or** on the enterprise plan."
</Tip>

## Percentage rollouts

Rollouts split traffic *after* a flag is enabled and has no matching rule for a given request. Rollout assignment is deterministic via FNV-1a hashing — the same `userId`/`tenantId` always lands in the same bucket.

| Strategy     | Bucketing key   | Requires                                                                 |
| ------------ | --------------- | ------------------------------------------------------------------------ |
| `per_user`   | `userId` hash   | `userId` present in evaluation context (or `userId` in the SDK call)     |
| `per_tenant` | `tenantId` hash | `tenantId` present in evaluation context (or `tenantId` in the SDK call) |

```json Example theme={null}
{
  "percentage": 25,
  "strategy": "per_user"
}
```

<Warning>
  If the bucketing key (`userId` for `per_user`, `tenantId` for `per_tenant`) is missing, the rollout treats the user as not in the rollout bucket — the flag falls through to its base value. Always pass the required attribute from your evaluation context.
</Warning>

## If an SDK key leaks

<Steps>
  <Step title="Rotate the key for that environment">
    From the dashboard: **Environment → Rotate key**. This is the same environment you set up in [Getting started, Step 1](/feature-flags/getting-started) — rotating only affects that one environment's key, not others.
  </Step>

  <Step title="Update the key everywhere it's stored">
    Replace the value in your secrets manager or environment variable — the same `FEATURE_FLAGS_SDK_KEY` referenced in [Getting started](/feature-flags/getting-started) and [Overview](/feature-flags/overview).
  </Step>

  <Step title="Redeploy or restart affected services">
    Any running process holding the old key in memory needs to pick up the new one.
  </Step>

  <Step title="Confirm the old key is dead">
    Try a call with the old key and confirm it now fails, rather than assuming rotation took effect.
  </Step>
</Steps>

<Warning>
  This assumes rotation invalidates the previous key immediately, with no overlap window — worth confirming, since a zero-downtime rotation needs a different rollout order if there *is* a grace period.
</Warning>

## Where this fits

<Columns cols={2}>
  <Card title="Overview" icon="flag" href="/feature-flags/overview">
    The environment → flag → SDK key mental model, plus a first `isEnabled()` / `useFeatureFlag()` call.
  </Card>

  <Card title="Getting started" icon="rocket" href="/feature-flags/getting-started">
    Ship `new_dashboard` end to end: create, enable, evaluate, verify.
  </Card>
</Columns>
