Feature Flags

Evaluate FastStats feature flags in the browser

Feature Flags are still in Private Preview please contact staff if you would like to try them out.

The feature flags extension loads flags for the visitor the analytics client tracks. After they load, flags are read synchronously from memory.

Enable Feature Flags

Create the extension, keep a reference to it, and add it to extensions.

flags.ts
import { init } from "@faststats/web";
import { featureFlags } from "@faststats/web/flags";

export const flags = featureFlags({
	flags: ["new_checkout", "theme", "max_items"],
});

init({
	siteKey: "your_site_key",
	extensions: [flags],
});

Flags load as soon as tracking starts. They load again when the visitor is identified or logs out, and when consent switches between "anonymous" and "granted". With consent "denied", no flags are loaded and every flag returns its default.

Options

OptionTypeDefaultDescription
flagsstring[]all flagsFlag keys this app reads
userIdstringnoneYour own ID for a signed-in user
attributesRecord<string, unknown>{}Values that targeting rules can match on
endpointstringhttps://flags.faststats.devBase URL of the feature flags service
timeoutMsnumber3000Aborts a slow load so flags serve their defaults
fetchtypeof fetchglobal fetchCustom fetch implementation, for example in tests

Every loaded flag is recorded as an exposure. List the keys your app reads in flags so flags the app never shows stay out of your analytics.

Read Flags

get returns the flag’s value, or defaultValue while flags are loading, when the flag does not exist or is archived, or when its type differs from the default’s type.

import { flags } from "./flags";

if (flags.isEnabled("new_checkout")) {
	// Show the new checkout.
}

const theme = flags.get("theme", "light");
const maxItems = flags.get("max_items", 10);

isEnabled(key) is the same as get(key, false). Use details(key) to see why a flag has its value. It returns undefined for a flag that is not loaded.

const result = flags.details("new_checkout");
// { value: true, variant: "on", reason: "rule_match" }
ReasonMeaning
disabledThe flag is turned off
prerequisite_failedA prerequisite flag did not match
target_matchThe visitor is targeted directly
rule_matchA targeting rule matched
fallthroughNo target or rule matched, so the default rule

Wait for Flags

Flags start out empty, so reads return their defaults until the first load finishes. Wait for ready() when a value must be correct before you render.

await flags.ready();
renderCheckout(flags.isEnabled("new_checkout"));

ready() never rejects. Check status to tell a successful load from a failed one.

StatusMeaning
idleThe client has not started tracking yet
loadingThe first load for the current visitor
readyFlags are loaded
errorThe load failed, so every flag returns its default

A failed load is logged when debug is on. Call reload() to try again. reload() never rejects either.

React to Changes

subscribe calls a listener whenever the flags or status change, and returns a function that removes it.

const unsubscribe = flags.subscribe(() => {
	updateCheckout(flags.isEnabled("new_checkout"));
});

Users and Attributes

Rollouts follow the visitor’s anonymous identifier. Once you call identify, flags reload with the external ID as userId, so rollouts follow a signed-in user across devices. logout clears it again.

Use setContext to change the user or attributes yourself. It loads the flags again and returns a promise that resolves when the load settles.

await flags.setContext({
	userId: "user_123",
	attributes: { plan: "pro", country: "DE" },
});

attributes replaces the attributes set before. Pass userId: null to go back to the anonymous visitor.