Local evaluation
Download flag definitions in the background and evaluate in-process, with no request per evaluation.
In local evaluation, the SDK downloads the complete flag definitions for its environment and evaluates them inside your process. It uses the same evaluation engine as the Toggld API, so the results are the same. Use local mode on long-lived servers.
import { ToggldClient } from "@toggld/node";
const flags = await ToggldClient.create({
sdkKey: process.env.TOGGLD_SDK_KEY!,
endpoint: process.env.TOGGLD_API_URL!,
mode: "local",
});Remote or local
| Remote (default) | Local | |
|---|---|---|
| How it evaluates | One HTTP request per evaluation | In your process, from a synchronized snapshot |
| Latency per evaluation | A network round trip | Microseconds |
| Works during an API outage | No. Calls return fallback values. | Yes, with the last snapshot it received |
| Startup | Ready immediately | Waits for the first snapshot |
| Credential | Secret key (ffk_) | Secret key (ffk_) |
| Data on your server | Evaluated values only | All rules, variants, and referenced segments for the environment |
| Telemetry | The API counts evaluations | The SDK sends counts in batches |
| Best for | Scripts, short-lived jobs, serverless functions | Long-lived servers |
Local mode needs a secret key, because the snapshot contains your targeting rules. Public tokens (ffp_) cannot download it.
How synchronization works
- On start, the SDK requests a snapshot from
GET /sync/v1/flags. Until the first snapshot arrives, it retries with a growing delay, from 250 ms up to 5 seconds. - With the default
wstransport, it also opens a WebSocket stream at/sync/v1/stream. The API pushes a new snapshot after each change, where the stream is available. - It keeps polling the snapshot every
sync.pollIntervalMs(default 30 seconds), even while the stream is healthy. Polling recovers changes that the stream missed. - It replaces its snapshot only when the snapshot version changes.
If a request fails, the SDK keeps its last snapshot and tries again. Evaluations continue to use the last snapshot.
Transports
sync.transport | Behavior |
|---|---|
"ws" (default) | Live stream plus polling. If the stream is not available, the SDK keeps polling and retries the stream with backoff, up to about 30 seconds between attempts. |
"poll" | Polling only. |
Hosted API uses polling
The hosted API at https://api.toggld.io does not serve the WebSocket stream yet, so SDKs receive updates by polling. The default ws transport falls back to polling automatically. Set sync: { transport: "poll" } to skip the stream connection attempts.
Readiness
ToggldClient.create waits for the first snapshot, for up to sync.readyTimeoutMs (default 5000 ms). Then it resolves even if no snapshot has arrived, and synchronization keeps trying in the background.
Until the first snapshot arrives:
- Typed getters return your fallback value.
- The details report
reason: "ERROR"anderrorCode: "PROVIDER_NOT_READY". evaluateAllreturns an empty array.
To wait for the first snapshot without a time limit, call ready():
const flags = await ToggldClient.create({ sdkKey, endpoint, mode: "local" });
await flags.ready(); // resolves when the first snapshot arrivesready() never resolves if the key is wrong, so add your own timeout if you use it at startup. In remote mode, ready() resolves at once.
How fast changes arrive
A saved change does not reach every SDK at once. Use these numbers to plan.
Local mode on the hosted API. The SDK polls every sync.pollIntervalMs. The hosted API can serve a snapshot from an edge cache for up to 60 seconds after a change. Plan for the poll interval, plus up to 60 seconds of cache age, plus request time. Changes often arrive sooner, but Toggld does not promise a fixed delivery time.
Remote mode on the hosted API. Each evaluation reads the API. The same edge cache can serve flag data for up to 60 seconds after a change.
Local mode on a self-hosted Node.js API. A change pushes to the SDKs that are connected to the same server process that saved it. Other processes learn about the change through polling. With successful requests, the conservative delivery bound is pollIntervalMs + 2 × timeoutMs, plus scheduling delays. With the defaults, that is 40 seconds. All API processes must read the same Postgres database. If you read from a replica, add its replication delay.
During network outages or API failures, there is no fixed delivery bound. The SDK converges after requests succeed again.
Get updates faster
Lower sync.pollIntervalMs to poll more often:
const flags = await ToggldClient.create({
sdkKey, endpoint, mode: "local",
sync: { pollIntervalMs: 10_000 },
});Each poll downloads a full snapshot. Snapshot polls and remote evaluations with secret keys share one rate limit of 1,200 requests per minute per environment. That budget covers every client that uses a secret key in the environment. For example, 100 local clients that poll every 10 seconds use 600 requests per minute. Use a positive, finite interval.
Performance
A loopback benchmark on a laptop compared the two modes with the same workload:
| Mode | Throughput | Median latency |
|---|---|---|
| Remote (OFREP over HTTP) | about 11,600 evaluations per second | 2.4 ms |
| Local (in-process engine) | about 1,090,000 evaluations per second | 27 µs |
These numbers come from a local test, not from the hosted API. Over the internet, remote evaluation also pays the network round trip.
Segments
The snapshot includes the segments that your flags reference, with their rules and member lists. Segment membership resolves in your process.
API runtimes
The Toggld API runs in two runtimes, and synchronization differs between them:
| API runtime | Stream | Updates |
|---|---|---|
Cloudflare Workers (the hosted API at api.toggld.io) | Not available yet | Polling |
| Node.js (self-hosted) | Available | Stream pushes plus polling |
The SDK itself targets Node.js 22 or later. It uses Node.js APIs, such as node:fs for flag files. Toggld does not document other runtimes for the SDK.
Telemetry
In network-synchronized local mode, the SDK counts evaluations and sends the counts in batches every 30 seconds and on close(). These counts feed Analytics. Remote mode sends no SDK telemetry, because the API counts those evaluations. See Telemetry.
Evaluate from a file
Local mode can also read its snapshot from a file instead of the network. Use this for local development, tests, and CI. See Flag files.