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 aLayer. Only this API needseffect. 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/effectis its Effect API.
Modes
ToggldClient has three modes, set with mode. The serverless client is a fourth way to evaluate.
| Mode | How it evaluates | Billable requests | Runtime |
|---|---|---|---|
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. | None | Long-lived servers |
File (mode: "file") | In your process, from a flag file on disk, watched for edits. See Flag files. | None, and no network | Local development, tests, CI |
Remote (mode: "remote") | One request to the Toggld API per evaluation. See When to use remote mode. | One per evaluation | Any 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. | None | Cloudflare 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
WebSocketthat Node.js 22 provides. Bun and Deno are expected to work, but Toggld does not test them yet. The serverless client needs onlyfetch. See Choosing a runtime and mode. - For the Effect API only:
effectversion 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/jsTo 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:
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/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 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 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 valuenull, 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:
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 | The 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_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 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.