On this page
SDK Documentation
Everything you need to install, configure, and evaluate feature flags with the Flaggy SDK.
Installation
Install the SDK from npm. One package covers both browser and Node.js environments.
npm install @flaggy.io/sdk-js Requirements
- Node.js 18.0.0 or higher
- Browser: any modern browser with ES2020 support
Quick Start
Three steps to evaluate your first flag.
Create the client
import { flaggy } from "@flaggy.io/sdk-js";
export const flagClient = flaggy({
apiKey: process.env.FLAGGY_API_KEY!,
}); Initialize before use
await flagClient.initialize(); Ensures flags are loaded before evaluation. On Node.js it waits for the first fetch. In the browser it returns immediately if a warm cache exists.
Check a flag
if (flagClient.isEnabled("new-checkout", { key: "user-123" })) {
// show new checkout
} Setup by Environment
React / Browser
// src/lib/flaggy.ts
import { flaggy } from "@flaggy.io/sdk-js";
export const flagClient = flaggy({
apiKey: import.meta.env.VITE_FLAGGY_API_KEY!,
environment: import.meta.env.VITE_ENVIRONMENT,
}); // src/main.tsx
await flagClient.initialize();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
); if (flagClient.isEnabled("new-header", { key: user.id, plan: user.plan })) {
return <NewHeader />;
} Node.js
// src/lib/flaggy.ts
import { flaggy } from "@flaggy.io/sdk-js";
export const flagClient = flaggy({
apiKey: process.env.FLAGGY_API_KEY!,
environment: process.env.NODE_ENV,
}); // src/server.ts
await flagClient.initialize();
app.listen(3000); if (flagClient.isEnabled("new-algorithm", { key: req.user.id, plan: req.user.plan })) {
// use new algorithm
} Configuration
Options passed to flaggy() when creating the client.
const flagClient = flaggy({
apiKey: "your-api-key", // required
environment: "production", // optional — defaults to "production"
onError: (err) => { // optional — called on fetch failure
console.warn(err);
},
}); | Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | — | Required. Your Flaggy API key. |
| environment | string | "production" | Target environment. Accepts production, staging, or development. Any other value falls back to production. |
| onError | (err) => void | — | Optional. Called on fetch failure. Does not throw. |
flaggy() across multiple modules returns the same instance. Safe to call from anywhere — only one client is created.
Initialization
Call client.initialize() once at application startup before evaluating any flags.
Browser
Flags are cached in localStorage. If a warm cache exists, initialize() resolves immediately and triggers a background refresh. If not, it waits for the first fetch.
Node.js
Cached in-memory per process. The cache is empty on restart, so initialize() always waits for the first fetch before resolving. Call it before accepting requests.
Checking Flags
isEnabled evaluates a flag and returns a boolean. Evaluation is local — no network call happens at the point of evaluation.
// Basic on/off flag
client.isEnabled("my-flag");
// With context for targeting
client.isEnabled("my-flag", { key: "user-123", plan: "pro" });
// With a custom default
client.isEnabled("my-flag", { key: "user-123" }, false); If the flag doesn't exist, isEnabled returns defaultValue (default: false). Flags with no segments return true when enabled — they are global on/off switches.
Context Object
The context is a flat key-value object describing the entity being evaluated. It determines which segments the entity belongs to.
The key field is required
It must be a stable string identifier for the entity — a user ID, company slug, device ID, or session ID. It is used for deterministic rollout bucketing and is hashed before storage.
User context
flagClient.isEnabled("new-dashboard", {
key: "user-123", // required — stable entity identifier
plan: "pro",
country: "US",
betaOptIn: true,
}); Company context
flagClient.isEnabled("enterprise-feature", {
key: "acme-corp",
plan: "enterprise",
company_size: 500,
}); Device context
flagClient.isEnabled("new-onboarding", {
key: "device-abc123",
platform: "ios",
app_version: "2.1.0",
}); All attribute values are coerced to strings before comparison. If an attribute is missing from the context, conditions that reference it evaluate to false.
Segment Targeting
Segments let you enable a flag only for entities that match specific attributes. Each segment has a rollout percentage and a set of conditions.
- All conditions within a segment must match — AND logic. e.g.
plan equals "pro"ANDcountry equals "US" - Segments are evaluated in priority order — the first matching segment wins.
- Rollout percentage is deterministic — the same entity always gets the same result (bucketed via MurmurHash3 on the
key).
Condition operators
| Operator | Description |
|---|---|
| equals | Attribute exactly matches the value |
| not_equals | Attribute does not match the value |
| contains | Attribute string contains the value |
| not_contains | Attribute string does not contain the value |
| starts_with | Attribute string starts with the value |
| ends_with | Attribute string ends with the value |
Evaluation Logic
When you call isEnabled, the SDK evaluates the flag in this order:
| Step | Condition | Result |
|---|---|---|
| 1 | Flag not found | defaultValue (default: false) |
| 2 | Flag is disabled | false |
| 3 | Flag has no segments | true — global on/off flag |
| 4 | Segments defined, no context passed | false |
| 5 | Context passed, no segment matches | false |
| 6 | Segment matches, entity outside rollout | false |
| 7 | Segment matches, entity inside rollout | true |
false. Always pass a context when evaluating targeted flags.
Analytics
The SDK automatically records flag evaluations and flushes them to Flaggy in the background. No configuration required.
Batched flushing
Events are sent every 5 seconds, or immediately when 100 events accumulate.
Per-session deduplication
Each unique combination of flag, entity key, and result is recorded once per session. Re-renders don't produce duplicates.
Page unload
In the browser, buffered events are flushed when the page unloads via navigator.sendBeacon.
Non-blocking
Analytics never block flag evaluation. Errors are swallowed silently.
Event shape
{
"flag_key": "new-feature",
"result": true,
"segment_matched": "beta-users",
"context_key": "user-123" // hashed before storage
} segment_matched is the key of the matching segment, or "no_match" if none matched. context_key is your entity key, hashed before storage.
Caching & Refresh
The SDK caches flags locally and refreshes them automatically in the background.
Browser
Stored in localStorage. Loaded immediately on startup; a stale cache triggers a background refresh without blocking evaluation.
Node.js
Cached in-memory per process. Empty on restart — initialize() always fetches before resolving.
API Reference
flaggy(config): FeatureFlagClient Creates (or returns) the global singleton client. Safe to call across modules — subsequent calls with the same API key return the same instance.
| Param | Type | Description |
|---|---|---|
| apiKey | string | Required. Your project API key. |
| environment | string | Optional. Defaults to production. |
| onError | (err) => void | Optional. Called on fetch failure, does not throw. |
client.initialize(): Promise<void> Ensures flags are ready. Call once at application startup before evaluating any flags. Resolves when the flag ruleset is available locally.
client.isEnabled(flagName, context?, defaultValue?): boolean Evaluates a flag locally. Returns defaultValue (false) if the flag doesn't exist. The key field in context is required when passing a context.
client.isEnabled(flagName: string, context?: object, defaultValue?: boolean): boolean client.getAllFlags(): Record<string, FeatureFlag> Returns a shallow copy of all currently cached flags. Useful for debugging or rendering a flag state panel.
const flags = client.getAllFlags();
// Returns: Record<string, FeatureFlag> — shallow copy of the cache Ready to get started?
Create a free account and have your first flag running in minutes.
Get started free →