Node.js SDK
Install @toggld/node, create a client, read typed flag values, and handle errors.
@toggld/node evaluates Toggld flags from Node.js servers. It has two APIs from one implementation:
@toggld/node: a Promise API. No Effect types appear in its signatures.@toggld/node/effect: an Effect API, with the client as a service and aLayer. See Effect API.
It has two evaluation modes:
- Remote (default): each evaluation is a request to the Toggld API.
- Local: the SDK downloads the flag definitions and evaluates in your process. Recommended for long-lived servers. See Local evaluation.
Requirements
- Node.js 22 or later. Local evaluation uses the global
WebSocketthat Node.js 22 provides. effectversion 4 (^4.0.5) as a peer dependency. Both APIs need it.- A secret key that starts with
ffk_. Use a server SDK key for evaluation. See API keys.
The package is ESM only.
Install
pnpm add @toggld/node effectThe SDK runs on your application's copy of Effect, so an Effect application shares one runtime with the SDK.
Configure credentials
Store the key and the API URL in your server environment:
TOGGLD_SDK_KEY=ffk_YOUR_SECRET_KEY
TOGGLD_API_URL=https://api.toggld.ioThe SDK does not read these variables by itself. Pass them to ToggldClient.create. For a self-hosted Toggld API, set TOGGLD_API_URL to your API origin.
Keep secret keys on trusted servers. Never bundle them into browser code. For client-side code, use a public token.
Create a client
Create one client when your process starts, and reuse it for every evaluation.
import { ToggldClient } from "@toggld/node";
export const flags = await ToggldClient.create({
sdkKey: process.env.TOGGLD_SDK_KEY!,
endpoint: process.env.TOGGLD_API_URL!,
mode: "local", // omit for remote evaluation
});create resolves when the client is ready. In local mode, it waits for the first flag snapshot, or for up to five seconds. See Readiness.
For every option, see Configuration.
Read flag values
Each flag type has a typed getter. Pass the flag key, a fallback value, and an evaluation context.
const enabled = await flags.getBooleanValue("new-checkout", false, {
targetingKey: "user-123",
plan: "pro",
});
if (enabled) {
// serve the new checkout
}| Flag type | Value method | Details method |
|---|---|---|
boolean | getBooleanValue | getBooleanDetails |
string | getStringValue | getStringDetails |
number | getNumberValue | getNumberDetails |
object | getObjectValue<T> | getObjectDetails<T> |
Each value method returns the flag value, or your fallback value if evaluation fails. It never throws.
const theme = await flags.getStringValue("checkout-theme", "light", { targetingKey: "user-123" });
const limit = await flags.getNumberValue("cart-item-limit", 25, { targetingKey: "user-123" });
const layout = await flags.getObjectValue("checkout-layout", { columns: 1 }, { targetingKey: "user-123" });Choose fallback values that are safe for users. Your code receives them during outages, before the client is ready, and when a flag is missing.
Inspect evaluation details
The details methods return the full OpenFeature result. Use them during integration and when you log or debug an evaluation.
const details = await flags.getBooleanDetails("new-checkout", false, {
targetingKey: "user-123",
});// details:
{
flagKey: "new-checkout",
value: true,
variant: "on",
reason: "TARGETING_MATCH",
errorCode: undefined,
errorMessage: undefined,
flagMetadata: { team: "growth" },
}| Field | Description |
|---|---|
flagKey | The key that you requested. |
value | The flag value, or your fallback value on error. |
variant | The served variant name. undefined on error. |
reason | STATIC, TARGETING_MATCH, SPLIT, DISABLED, or ERROR. See reasons. |
errorCode | An OpenFeature error code when reason is ERROR. |
errorMessage | A human-readable explanation of the error. |
flagMetadata | The flag's metadata. |
evaluate(key, fallback, context) returns the same details without a type check. Use it when you do not know the flag type in advance.
Evaluate every flag
evaluateAll(context) returns the details for every flag in the environment.
const all = await flags.evaluateAll({ targetingKey: "user-123" });
for (const flag of all) {
console.log(flag.flagKey, flag.value, flag.reason);
}- In remote mode, it sends one request to the bulk OFREP endpoint.
- In local mode, it evaluates in your process.
- An entry for a flag that failed has
reason: "ERROR"and the valuenull, because there is no per-flag fallback. - If the whole request fails, or the local client is not ready yet, it returns an empty array.
Shared context
Use defaultContext for attributes that apply to every evaluation, such as a region or a service version. The context of each call is merged on top. When both define the same attribute, the per-call value wins.
const flags = await ToggldClient.create({
sdkKey: process.env.TOGGLD_SDK_KEY!,
endpoint: process.env.TOGGLD_API_URL!,
defaultContext: { region: "eu-west", service: "checkout-api" },
});Close the client
Call close() when your process shuts down. It stops background synchronization, sends the remaining telemetry, and releases resources. Do not use the client after you close it.
process.on("SIGTERM", async () => {
await flags.close();
process.exit(0);
});Error handling
Evaluation never throws. If anything goes wrong, the call resolves to your fallback value. The details methods report what happened in errorCode:
errorCode | Cause |
|---|---|
FLAG_NOT_FOUND | No flag with this key exists in the key's environment. |
TYPE_MISMATCH | The flag value does not match the getter, for example getBooleanValue on a string flag. |
TARGETING_KEY_MISSING | A rollout rule matched, but the context has no usable bucketing key. See Missing bucketing key. |
PROVIDER_NOT_READY | Local mode has not received its first snapshot yet. |
PARSE_ERROR | The SDK could not decode the API response. |
GENERAL | A network error, a timeout, a server error, a rejected request, or another failure. A rejected request is, for example, HTTP 401 for a wrong key or HTTP 429 for a rate limit. errorMessage then names the HTTP status. |
Check reason === "ERROR" or errorCode !== undefined to detect a failure:
const details = await flags.getBooleanDetails("new-checkout", false, { targetingKey: userId });
if (details.errorCode) {
logger.warn({ flag: details.flagKey, code: details.errorCode, message: details.errorMessage }, "flag fallback");
}A wrong or revoked key does not throw either. In remote mode, each call returns the fallback value with GENERAL and an errorMessage that names HTTP 401. In local mode, the client never becomes ready, and each call returns the fallback value with PROVIDER_NOT_READY. Check the details during integration so that a wrong key does not go unnoticed.
Other languages
Toggld ships an SDK for Node.js only. Go, Python, Ruby, and other SDKs are not available. For other languages, see OpenFeature and OFREP.