# Flags in your code (/docs/feature-flags/in-code)

Every flag gives your code one of these values:

| Value | Meaning |
| --- | --- |
| `true` / `false` | Boolean flag: on or off for this user. |
| `"control"`, `"test"`, … | Multivariate flag or experiment: the variant. |
| `undefined` | Unknown flag, the flag is off, or flags haven't loaded yet. Show the default. |

## Browser [#browser]

```ts
const variant = datahop.getFeatureFlag('new-checkout')

if (variant === 'test') renderNewCheckout()
else renderOldCheckout() // 'control', false or undefined
```

Flags load when the page starts, and again after `identify`, `group` and `reset`, so targeting by plan or company applies right away. Returning visitors get the previous values instantly while fresh ones load. On a first visit, flags are `undefined` for a moment. To render only once they're in:

```ts
const unsubscribe = datahop.onFeatureFlags((flags) => {
  setShowBanner(flags['annual-discount-banner'] === 'test')
})
```

In React, call it in an effect and return `unsubscribe` as the cleanup. After you change a person's properties, call `datahop.reloadFeatureFlags()` to pick up flags that depend on them.

| Method | Returns | Counts as exposure |
| --- | --- | --- |
| `getFeatureFlag(key)` | The value | Yes |
| `isFeatureEnabled(key)` | `true` for on or any variant | Yes |
| `getFeatureFlagPayload(key)` | The payload for the user's value | No |
| `getAllFlags()` | Every flag | No |

Pass `{ sendEvent: false }` to `getFeatureFlag` or `isFeatureEnabled` to read a flag without recording an exposure.

## Server (Node / Bun) [#server-node--bun]

```ts
const variant = await datahop.getFeatureFlag({
  distinctId: user.id,
  key: 'new-pricing-page',
  groups: { company: user.orgId },     // needed for flags that match by company
  personProperties: { plan: 'pro' },   // values you know but haven't sent yet
})
```

Reading several flags for the same user in one request costs one network call. If Datahop can't be reached within a few seconds, you get `undefined`, so always handle the default.

## Exposures and experiments [#exposures-and-experiments]

Reading a flag with `getFeatureFlag` or `isFeatureEnabled` records that the user **saw** it. [Experiments](/docs/experiments) only count people who were exposed. So:

- **Read the flag where the experience starts**, such as on the pricing page and not on app start. Otherwise people who never reached the feature dilute the result.
- **Handle every variant and `undefined`.** Show `control` when in doubt.
- **Logging in doesn't reshuffle anyone.** A visitor who saw `test` before signing up still sees `test` after.

Once flags are loaded, the browser SDK also adds the user's flag values to every event. That lets you break down any insight by a flag, such as orders by `$feature/new-checkout`, without running an experiment.

## Override flags while testing [#override-flags-while-testing]

Force values in your own browser for QA, demos or reproducing a bug:

```ts
datahop.overrideFeatureFlags({ 'new-checkout': 'test', 'dark-mode': true })
datahop.overrideFeatureFlags(false) // clear
```

Overrides stay until you clear them. They aren't recorded as exposures, so testing in your own browser never skews an experiment.

## Other languages [#other-languages]

`POST /i/flags` returns every flag for a user. See the [SDK reference](/docs/reference/sdk#http-endpoints). If you use a value in an experiment, also send a `$feature_flag_called` event so the exposure counts.
