Skip to content

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.

Terminal window
npm install @feathq/web-sdk
# or
yarn add @feathq/web-sdk

For 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); // sync
const 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.

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.

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.

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.

  • Pre-evaluation. Every flag is evaluated against the current context into a Map at 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 BroadcastChannel to 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.
  • dataPlaneUrl must be https://. Plaintext is rejected except http://localhost for tests.
  • cache: { storage: "localStorage" } persists the full datafile (including rules and segment definitions) under feat:datafile. Default is off; enable it only if you are comfortable with the footprint.
  • anonymous: { storage: "localStorage" } writes a stable UUID to feat:anonymousKey. Use storage: "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.