Browser SDK
@feathq/web-sdk is the browser SDK. It polls the datafile to the browser, pre-evaluates every flag against the current context into a map, and lets you read values synchronously after init.
Install
Section titled “Install”npm install @feathq/web-sdk# oryarn add @feathq/web-sdkFor server code use @feathq/js-sdk. For OpenFeature on the web, install @feathq/openfeature-web alongside this package.
import { FeatWebClient } from "@feathq/web-sdk";
const client = new FeatWebClient({ apiKey: "feat_cs_…", // client-side ID dataPlaneUrl: "https://data.feat.so", anonymous: { storage: "localStorage" }, // optional: stable anonymous user cache: { storage: "localStorage" }, // optional: warm cache across loads});
await client.ready();
const enabled = client.getBooleanValue("checkout-v2", false); // syncconst greeting = client.getStringValue("hero-greeting", "Hi");Use a client-side ID (feat_cs_…). It is safe to embed in your bundle. Add your site’s origin to the key’s authorized URLs in the feat console.
Reacting to flag changes
Section titled “Reacting to flag changes”client.on("change", ({ flagKey, newValue }) => { console.log(`${flagKey} → ${newValue}`);});
await client.setContext({ targetingKey: "user-123", user: { plan: "pro" },});change fires per flag whose evaluated value flipped, after either a context change or a datafile refresh. Use it to re-render parts of your UI.
OpenFeature
Section titled “OpenFeature”import { OpenFeature } from "@openfeature/web-sdk";import { FeatWebClient } from "@feathq/web-sdk";import { FeatWebProvider } from "@feathq/openfeature-web";
const featClient = new FeatWebClient({ apiKey, dataPlaneUrl });await OpenFeature.setProviderAndWait(new FeatWebProvider(featClient));await OpenFeature.setContext({ targetingKey: "user-123" });
const enabled = OpenFeature.getClient().getBooleanValue("checkout-v2", false);See OpenFeature.
Server-side rendering
Section titled “Server-side rendering”Fetch the datafile on the server and hand it to the client to skip the first round trip:
new FeatWebClient({ apiKey, dataPlaneUrl, bootstrap: serverProvidedDatafile });getBooleanValue and friends work synchronously from the first paint.
How it works
Section titled “How it works”- Pre-evaluation. Every flag is evaluated against the current context into a
Mapat init and on each datafile refresh, so reads are O(1) and synchronous. - Visibility-aware polling. Polls every 30 seconds by default. Pauses while the tab is hidden. Force-refreshes when the tab regains visibility.
- Cross-tab sync. Uses
BroadcastChannelto share a fresh datafile across same-origin tabs. One tab fetches; siblings adopt without their own request. - 304-aware. Sends
If-None-Match; unchanged polls return 304. dataPlaneUrlmust behttps://. Plaintext is rejected excepthttp://localhostfor tests.
Security notes
Section titled “Security notes”cache: { storage: "localStorage" }persists the full datafile (including rules and segment definitions) underfeat:datafile. Default is off; enable it only if you are comfortable with the footprint.anonymous: { storage: "localStorage" }writes a stable UUID tofeat:anonymousKey. Usestorage: "memory"if you do not want it persisted.BroadcastChannel("feat:datafile")is same-origin. Any script on the same origin can subscribe; treat the datafile as same-origin-readable.
Related
Section titled “Related”- API keys for what makes the client-side ID safe.
- Contexts for the input shape.
- OpenFeature for the standardized API.