Browser and React
Read public flags in the browser with @coframe/sdk/feature-flags and the React hooks in @coframe/sdk/react
Copy this guide and your current settings as a prompt for your AI agent.
Preview agent promptSee the guide, code and settings included when you copy.
Flags are separate npm entry points of @coframe/sdk (1.1.24 or newer). The
cf.js script tag does not contain them, and nothing is fetched, cached or
listened to until featureFlags.init() runs. The browser only ever receives
public flags; see Public and private.
npm install @coframe/sdkpnpm add @coframe/sdkyarn add @coframe/sdkbun add @coframe/sdknub add @coframe/sdkaube add @coframe/sdk01Browser
import { setUserToken } from '@coframe/sdk'
import {
featureFlags,
isFeatureEnabled,
getFeatureFlag,
getFeatureFlagPayload,
onFeatureFlags,
} from '@coframe/sdk/feature-flags'
setUserToken('customer-123') // your stable user id
await featureFlags.init({
projectId: 'your-project-id',
selector: { names: ['checkout-redesign'], groups: ['homepage_campaigns'] },
})
onFeatureFlags(() => {
if (isFeatureEnabled('checkout-redesign')) showNewCheckout()
const layout = getFeatureFlag('checkout-layout') // 'control' | 'compact' | …
const content = getFeatureFlagPayload('homepage-content')
})| Function | Returns |
|---|---|
featureFlags.init(options) |
Starts loading. options: projectId (required), userToken, userContext, selector, consent, reloadOnSpaNavigation (default true), requestTimeoutMs (default 3000), cacheTtlMs (default 5 minutes) |
isFeatureEnabled(key) |
true when a toggle is on or a variant is assigned; false otherwise; undefined before results land |
getFeatureFlag(key) |
true/false for a toggle, the variant key for a variant flag, undefined before results land |
getFeatureFlagPayload(key) |
The flag’s payload, or the assigned variant’s payload |
getRemoteConfig(key) |
Same as getFeatureFlagPayload, named for remote config |
getFeatureFlagResult(key) |
The full result: enabled, reason, variant, payload, metadata.version |
onFeatureFlags(callback) |
Runs when results arrive or change; returns an unsubscribe function |
reloadFeatureFlags() |
Re-evaluates now |
setUserToken(token), getUserToken(), reset(), setConsent(enabled) |
Identity and consent, also exported from @coframe/sdk/feature-flags |
How the browser behaves
| Behaviour | Detail |
|---|---|
| Timeout | 3 s by default (requestTimeoutMs). Loading never delays the page |
| Cache | localStorage key coframe.featureFlags.<projectId>, 5 minutes (cacheTtlMs), written only after consent, keyed by a hash of token and context. A fresh cached answer shows immediately; a failed fetch falls back to it |
| SPA navigation | Re-evaluates on pushState, replaceState, popstate and hashchange because conditions can target the URL. Turn off with reloadOnSpaNavigation: false |
| Identity change | setUserToken clears results and fetches again |
Who is this visitor?
Rollouts and variant splits hash the user token, so give every user a stable one. In the browser the first match wins:
init({ userToken })- the latest queued
setUserToken()(alsowindow.CFQ.push({ setUserToken })) - the
coframe.user.tokencookie - a new random UUID
The cookie is only written after consent (setConsent(true) or
window.CFQ.push({ cookiesEnabled: true })). Before consent a generated token
changes on every page load, so a 10 % rollout can answer differently each time.
Pass your own user or session id whenever you have one.
02React
import { featureFlags } from '@coframe/sdk/feature-flags'
import { useFeatureFlag, useFeatureFlagPayload } from '@coframe/sdk/react'
featureFlags.init({ projectId: 'your-project-id' }) // once, at startup
function Checkout() {
const enabled = useFeatureFlag('checkout-redesign')
const config = useFeatureFlagPayload('checkout-redesign')
if (enabled === undefined) return <CheckoutSkeleton />
return enabled ? <NewCheckout config={config} /> : <OldCheckout />
}| Hook | Returns |
|---|---|
useFeatureFlagEnabled(key) |
boolean | undefined |
useFeatureFlagVariantKey(key), alias useFeatureFlag(key) |
true/false for a toggle, the variant key for a variant flag, undefined before load |
useFeatureFlagPayload(key) |
The payload, or the assigned variant’s payload |
useFeatureFlagResult(key) |
The full result object |
The hooks read the same state as @coframe/sdk/feature-flags.
CoframeProvider does not start flag loading on its own: call
featureFlags.init once.
Multiple variants in code
const layout = getFeatureFlag('checkout_experience_v1') // "standard" | "compact" | "express" | false | undefined
const config = getFeatureFlagPayload('checkout_experience_v1') // { layout: "compact", cta: "Finish checkout" }isFeatureEnabled(key) is true whenever a variant is assigned. Variants are
not linked to Coframe experiments: no exposure events are sent and nothing
appears in experiment reporting. Use a Coframe experiment when you need
measured impact.
# Browser and React
Read public flags in the browser with @coframe/sdk/feature-flags and the React hooks in @coframe/sdk/react
Flags are separate npm entry points of `@coframe/sdk` (1.1.24 or newer). The
`cf.js` script tag does not contain them, and nothing is fetched, cached or
listened to until `featureFlags.init()` runs. The browser only ever receives
public flags; see [Public and private](../docs/public-and-private.html).
BASH
```
npm install @coframe/sdk
```
BASH
```
pnpm add @coframe/sdk
```
BASH
```
yarn add @coframe/sdk
```
BASH
```
bun add @coframe/sdk
```
BASH
```
nub add @coframe/sdk
```
BASH
```
aube add @coframe/sdk
```
## Browser
TS
```
import { setUserToken } from '@coframe/sdk'
import {
featureFlags,
isFeatureEnabled,
getFeatureFlag,
getFeatureFlagPayload,
onFeatureFlags,
} from '@coframe/sdk/feature-flags'
setUserToken('customer-123') // your stable user id
await featureFlags.init({
projectId: 'your-project-id',
selector: { names: ['checkout-redesign'], groups: ['homepage_campaigns'] },
})
onFeatureFlags(() => {
if (isFeatureEnabled('checkout-redesign')) showNewCheckout()
const layout = getFeatureFlag('checkout-layout') // 'control' | 'compact' | …
const content = getFeatureFlagPayload('homepage-content')
})
```
Function |
Returns |
`featureFlags.init(options)` |
Starts loading. `options`: `projectId` (required), `userToken`, `userContext`, `selector`, `consent`, `reloadOnSpaNavigation` (default `true`), `requestTimeoutMs` (default 3000), `cacheTtlMs` (default 5 minutes) |
`isFeatureEnabled(key)` |
`true` when a toggle is on or a variant is assigned; `false` otherwise; `undefined` before results land |
`getFeatureFlag(key)` |
`true`/`false` for a toggle, the variant key for a variant flag, `undefined` before results land |
`getFeatureFlagPayload(key)` |
The flag’s payload, or the assigned variant’s payload |
`getRemoteConfig(key)` |
Same as `getFeatureFlagPayload`, named for remote config |
`getFeatureFlagResult(key)` |
The full result: `enabled`, `reason`, `variant`, `payload`, `metadata.version` |
`onFeatureFlags(callback)` |
Runs when results arrive or change; returns an unsubscribe function |
`reloadFeatureFlags()` |
Re-evaluates now |
`setUserToken(token)`, `getUserToken()`, `reset()`, `setConsent(enabled)` |
Identity and consent, also exported from `@coframe/sdk/feature-flags` |
undefined means not loaded yet
Before the first answer arrives the getters return `undefined`, not `false`.
Branch on `undefined` to show a neutral state instead of the “off” experience.
How the browser behaves
Behaviour |
Detail |
Timeout |
3 s by default (`requestTimeoutMs`). Loading never delays the page |
Cache |
`localStorage` key `coframe.featureFlags.<projectId>`, 5 minutes (`cacheTtlMs`), written only after consent, keyed by a hash of token and context. A fresh cached answer shows immediately; a failed fetch falls back to it |
SPA navigation |
Re-evaluates on `pushState`, `replaceState`, `popstate` and `hashchange` because conditions can target the URL. Turn off with `reloadOnSpaNavigation: false` |
Identity change |
`setUserToken` clears results and fetches again |
Who is this visitor?
Rollouts and variant splits hash the user token, so give every user a stable
one. In the browser the first match wins:
- `init({ userToken })`
- the latest queued `setUserToken()` (also `window.CFQ.push({ setUserToken })`)
- the `coframe.user.token` cookie
- a new random UUID
The cookie is only written after consent (`setConsent(true)` or
`window.CFQ.push({ cookiesEnabled: true })`). Before consent a generated token
changes on every page load, so a 10 % rollout can answer differently each time.
Pass your own user or session id whenever you have one.
## React
TSX
```
import { featureFlags } from '@coframe/sdk/feature-flags'
import { useFeatureFlag, useFeatureFlagPayload } from '@coframe/sdk/react'
featureFlags.init({ projectId: 'your-project-id' }) // once, at startup
function Checkout() {
const enabled = useFeatureFlag('checkout-redesign')
const config = useFeatureFlagPayload('checkout-redesign')
if (enabled === undefined) return <CheckoutSkeleton />
return enabled ? <NewCheckout config={config} /> : <OldCheckout />
}
```
Hook |
Returns |
`useFeatureFlagEnabled(key)` |
`boolean | undefined` |
`useFeatureFlagVariantKey(key)`, alias `useFeatureFlag(key)` |
`true`/`false` for a toggle, the variant key for a variant flag, `undefined` before load |
`useFeatureFlagPayload(key)` |
The payload, or the assigned variant’s payload |
`useFeatureFlagResult(key)` |
The full result object |
The hooks read the same state as `@coframe/sdk/feature-flags`.
`CoframeProvider` does not start flag loading on its own: call
`featureFlags.init` once.
Multiple variants in code
TS
```
const layout = getFeatureFlag('checkout_experience_v1') // "standard" | "compact" | "express" | false | undefined
const config = getFeatureFlagPayload('checkout_experience_v1') // { layout: "compact", cta: "Finish checkout" }
```
`isFeatureEnabled(key)` is `true` whenever a variant is assigned. Variants are
not linked to Coframe experiments: no exposure events are sent and nothing
appears in experiment reporting. Use a Coframe experiment when you need
measured impact.