Dashboard
JavaScript (server)

JavaScript server SDK

Install @toggld/js, create a client, read typed flag values, and handle errors.

@toggld/js evaluates Toggld flags from server-side JavaScript: Node.js servers, and serverless and edge runtimes such as Cloudflare Workers, Vercel, and AWS Lambda.

@toggld/js was called @toggld/node before version 0.9.0. Replace the package name in your imports and in package.json. The modes have new names too: mode: "local" is now mode: "sync" (the default), and sync: { file } is now mode: "file", file. The old forms throw a TypeError. @toggld/node gets no more releases.

It has these entries:

  • @toggld/js: a Promise API. It has no runtime dependencies.
  • @toggld/js/effect: an Effect API, with the client as a service and a Layer. Only this API needs effect. See Effect API.
  • @toggld/js/serverless: the serverless client for Cloudflare Workers, Vercel, AWS Lambda, and other runtimes that run code only while they handle a request. @toggld/js/serverless/effect is its Effect API.

Modes

ToggldClient has three modes, set with mode. The serverless client is a fourth way to evaluate.

ModeHow it evaluatesBillable requestsRuntime
Sync (mode: "sync", default)In your process, from a copy of the flag rules that a stream and a poll keep in sync. See Sync mode.NoneLong-lived servers
File (mode: "file")In your process, from a flag file on disk, watched for edits. See Flag files.None, and no networkLocal development, tests, CI
Remote (mode: "remote")One request to the Toggld API per evaluation. See When to use remote mode.One per evaluationAny runtime; public tokens
On-demand (@toggld/js/serverless)In your process, from a copy in memory that requests refresh when it gets old. See Serverless client.NoneCloudflare Workers, Vercel, AWS Lambda

To choose, see Choosing a runtime and mode.

Requirements

  • Node.js 22 or later for the main entry. Sync mode uses the global WebSocket that Node.js 22 provides. Bun and Deno are expected to work, but Toggld does not test them yet. The serverless client needs only fetch. See Choosing a runtime and mode.
  • For the Effect API only: effect version 4 (^4.0.5). It is an optional peer dependency, so the Promise API works without it.
  • A secret key that starts with ffk_. Use a server SDK key for evaluation. See API keys. Sync mode needs a secret key, because the SDK downloads your targeting rules.
  • For sync mode, a long-lived process, such as a web server, with network access to the Toggld API. On serverless and edge runtimes, use the serverless client.

The package is ESM only.

Install

pnpm add @toggld/js

To use the Effect API, also install effect. The Effect API 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:

.env
TOGGLD_SDK_KEY=ffk_YOUR_SECRET_KEY
TOGGLD_API_URL=https://api.toggld.io

The 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.

flags.ts
import { ToggldClient } from "@toggld/js";

export const flags = await ToggldClient.create({
  sdkKey: process.env.TOGGLD_SDK_KEY!,
  endpoint: process.env.TOGGLD_API_URL!,
});

The client runs in sync mode. create waits for the first flag snapshot, for up to five seconds, and then resolves. After that, each evaluation runs in your process. See Readiness.

create throws a TypeError if the client can never download the snapshot: the key is missing, the key is a public token (ffp_), or endpoint is not an http or https URL. With a public token, pass mode: "remote".

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 typeValue methodDetails method
booleangetBooleanValuegetBooleanDetails
stringgetStringValuegetStringDetails
numbergetNumberValuegetNumberDetails
objectgetObjectValue<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" },
}
FieldDescription
flagKeyThe key that you requested.
valueThe flag value, or your fallback value on error.
variantThe served variant name. undefined on error.
reasonSTATIC, TARGETING_MATCH, SPLIT, DISABLED, or ERROR. See reasons.
errorCodeAn OpenFeature error code when reason is ERROR.
errorMessageA human-readable explanation of the error.
flagMetadataThe 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 sync and file modes, it evaluates in your process.
  • In remote mode, it sends one request to the bulk OFREP endpoint.
  • An entry for a flag that failed has reason: "ERROR" and the value null, because there is no per-flag fallback.
  • If the whole request fails, or the client has no snapshot 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.

In sync mode, the open stream keeps the Node.js process running. A script must call close() when it is done, or it does not exit.

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:

errorCodeCause
FLAG_NOT_FOUNDNo flag with this key exists in the key's environment.
TYPE_MISMATCHThe flag value does not match the getter, for example getBooleanValue on a string flag.
TARGETING_KEY_MISSINGA rollout rule matched, but the context has no usable bucketing key. See Missing bucketing key.
PROVIDER_NOT_READYThe client has no snapshot yet: sync mode has not received its first one, or file mode could not read the file. errorMessage names the last synchronization error, if there was one.
PARSE_ERRORThe SDK could not decode the API response.
GENERALA 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 sync mode, the client never becomes ready. create logs a warning that names HTTP 401 after five seconds, and each call returns the fallback value with PROVIDER_NOT_READY and an errorMessage that names HTTP 401. In remote mode, each call returns the fallback value with GENERAL and an errorMessage that names HTTP 401. 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.

On this page