Browser SDK
@lucerna-dev/gates-browser shows and hides UI with flags, kill switches and experiments. It fetches decisions for the current user once — your targeting rules never reach the browser — and answers every check instantly from memory.
Setup
import { createGates } from "@lucerna-dev/gates-browser";
import { createIdentity } from "@lucerna-dev/identity";
export const identity = createIdentity({ apiKey: "ck_client_prod_…" });
export const gates = createGates({
clientKey: "ck_client_prod_…",
identity,
});Options
| Option | Default | What it does |
|---|---|---|
clientKey | — | Your public client key (ck_client_…). Required. |
identity | — | The current user. Decisions refetch whenever it changes. |
baseUrl | https://api.uselucerna.app | Override for local development. |
storage | — | e.g. localStorage — caches decisions for instant next load. |
storageKey | lucerna:gates | Cache entry name. |
requestTimeoutMs | 5000 | Timeout per fetch. |
onError | — | Called when a fetch fails. Wire it to your logging. |
fetch | global fetch | Custom transport, mostly for tests. |
Checking gates
Checks take no arguments — they answer for the bound identity:
| Method | Returns | If unknown or not loaded |
|---|---|---|
gates.flag(key) | boolean | false |
gates.experiment(key) | variant name or null | null |
gates.switch(key) | boolean — false means killed | true (not killed) |
gates.decisions() | the full snapshot | undefined |
When decisions update
- On identity change —
identify(),trait()orreset()trigger one refetch (bursts are batched). Switching to a different user clears the old decisions immediately — no flash of someone else's UI. - On
gates.refresh()— call it yourself when it matters, e.g. after checkout.
There is no background poll in the browser. A dashboard change is picked up on the next page load, identity change or refresh().
Caching
Pass storage: localStorage and the last decisions render instantly on the next page load while fresh ones fetch. The cache is keyed to the user it was computed for — another user (or a new anonymous id) starts clean.
Frameworks
Everything React lives under the /react subpath:
import {
GatesProvider,
Feature,
KillSwitch,
Experiment,
Variant,
useFlag,
useExperiment,
useKillSwitch,
useGates,
} from "@lucerna-dev/gates-browser/react";<GatesProvider client={gates}>— once, at the root.<Feature name fallback>— renders children while the flag is on.<KillSwitch name fallback>— children while the path is alive, fallback when killed.<Experiment name fallback>+<Variant name>— renders the assigned variant; fallback when not in the experiment.useFlag(key)/useExperiment(key)/useKillSwitch(key)— re-render when decisions change.useGates()— the client itself, forrefresh().
Full component semantics, SSR notes and per-framework setup live in the framework guides: React, Astro, Preact.
Devtools
An in-page widget for development — live decisions with plain-language evaluation explanations, local override toggles, identity impersonation — with zero bytes in production builds. See Devtools.
When things go wrong
Reads never throw. Before the first load — or if a refetch fails — checks answer safe defaults (false / null / not killed) and keep serving the last decisions they have. ready() rejects on a bad client key; onError fires on every failed fetch.
UX only
Browser decisions show and hide UI. Authorization lives on your backend — verify with the Node SDK or your own checks.