Dashboard
JavaScript (server)

Sync mode

The default mode. Keep the flag rules in sync in the background and evaluate in your process, with no request per evaluation. When to use remote mode instead.

Sync mode (mode: "sync") is the default mode of the JavaScript server SDK. The SDK downloads the complete flag rules for its environment, keeps that copy in sync with Toggld, and evaluates flags inside your process. It uses the same evaluation engine as the Toggld API, so the results are the same.

import { ToggldClient } from "@toggld/js";

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

You do not need to set mode. To send each evaluation to the API instead, pass mode: "remote". See When to use remote mode. To evaluate from a flag file on disk, pass mode: "file". See Flag files.

In @toggld/node, this mode was called "local". In @toggld/js, mode: "local" throws a TypeError. Use mode: "sync", or leave mode out.

Why sync is the default

  • Fast. An evaluation takes microseconds. It does not wait for the network.
  • Works offline after the first sync. If the API is unreachable, the SDK keeps evaluating with the last snapshot that it received.
  • No request per evaluation. Evaluations never reach the API. They are not billable evaluation requests, and they do not use your rate limit. Remote mode sends one request for each evaluation.

Remote or sync

RemoteSync (default)
How it evaluatesOne HTTP request per evaluationIn your process, from a synchronized snapshot
Latency per evaluationA network round tripMicroseconds
Works during an API outageNo. Calls return fallback values.Yes, with the last snapshot it received
StartupReady immediatelyWaits for the first snapshot
CredentialSecret key (ffk_) or public token (ffp_)Secret key (ffk_)
Data on your serverEvaluated values onlyAll rules, variants, and referenced segments for the environment
TelemetryThe API counts evaluationsThe SDK sends counts in batches
Billable requestsOne per evaluation callNone
Best forPublic tokens, runtimes without memory between requestsLong-lived servers

On Cloudflare Workers, Vercel, and AWS Lambda, use the serverless client. It also evaluates in your code, from a copy of the flag rules refreshed on demand. See Choosing a runtime and mode.

Sync mode needs a secret key, because the snapshot contains your targeting rules. Public tokens (ffp_) cannot download it.

What sync mode needs

  • A secret key that starts with ffk_: a server SDK key or a management key. If the key is a public token (ffp_) or is missing, or if endpoint is not an http or https URL, create throws a TypeError at once. The message says what to change.
  • Network access to the Toggld API: HTTPS requests to GET /sync/v1/flags, and a WebSocket connection to /sync/v1/stream. If a proxy or the runtime blocks the WebSocket, the SDK keeps working with polling only. See Transports.
  • A long-lived process, such as a web server or a queue worker. Synchronization runs in the background for as long as the client is open.

When to use remote mode

Pass mode: "remote" to send each evaluation to the API:

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

Use remote mode in these cases:

  • Runtimes that keep no memory between requests, such as Fastly Compute. The serverless client would download the snapshot on every request there.
  • Processes that must not hold your targeting rules, for example code on machines that you do not control. Remote mode receives evaluated values only.
  • A public token (ffp_). Public tokens can evaluate through the API, but they cannot download definitions.

On Cloudflare Workers, Vercel, AWS Lambda, and Netlify, the serverless client evaluates in your code instead, with no billable requests. See Choosing a runtime and mode.

A remote client is ready at once. It sends one evaluation request for each call to a getter, to evaluate, or to evaluateAll. Remote evaluations are billable requests.

The main entry checks two runtimes in sync mode:

  • Cloudflare Workers and Vercel Edge: create throws a TypeError, because these runtimes cannot keep a stream or a poll open between requests. The message names the serverless client and mode: "remote".
  • AWS Lambda: the client starts, and logs one warning. Lambda freezes the process between invocations, so the stream and the poll stop, and after a freeze an evaluation can use a snapshot of any age.

Short scripts

Scripts and short-lived jobs work in any mode. In the default sync mode:

  • create waits for the first snapshot, so the first evaluation already has data.
  • Call close() at the end. The open stream keeps the Node.js process running until you close the client.
const flags = await ToggldClient.create({ sdkKey, endpoint });
console.log(await flags.getBooleanValue("new-checkout", false, { targetingKey: "user-123" }));
await flags.close(); // stops the sync, so the script can exit

How synchronization works

  1. 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.
  2. With the default ws transport, it also opens a WebSocket stream at /sync/v1/stream. The API pushes a new snapshot after each change, where the stream is available.
  3. It keeps polling the snapshot while the stream is healthy. Polling recovers changes that the stream missed. It polls every sync.pollIntervalMs (default 30 seconds). Against the hosted API, a healthy stream relaxes this to every sync.streamPollIntervalMs (default 5 minutes). See Poll intervals.
  4. It replaces its snapshot only when the snapshot version changes. A poll sends the version that the SDK has in If-None-Match. If nothing changed, the API answers 304 Not Modified with no body.

If a request fails, the SDK keeps its last snapshot and tries again. Evaluations continue to use the last snapshot.

Transports

sync.transportBehavior
"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, every sync.pollIntervalMs.

Heartbeat

The stream has a heartbeat. Every 30 seconds, the SDK sends a small "ping" on the stream. If no answer comes within 10 seconds, the SDK closes the connection and reconnects. This finds a connection that a proxy or a network device dropped without notice.

The SDK sends pings only to a server that says it answers them. An older self-hosted server does not say so, and gets no pings. Its connections are never closed for a missing answer.

Poll intervals

Each snapshot on the stream says how reliably the server pushes changes:

  • The hosted API promises durable delivery. It records every change that it has not pushed yet, and retries it.
  • A self-hosted Node.js API promises only best-effort delivery. A process can miss a change that another process saved.

The SDK polls every sync.streamPollIntervalMs (default 5 minutes) only while the stream is healthy and the server promises durable delivery. Healthy means that the stream is open, has sent a snapshot, and has answered a heartbeat. In every other case, the SDK polls every sync.pollIntervalMs (default 30 seconds).

When the stream drops or misses a heartbeat, the SDK returns to sync.pollIntervalMs at once. If its last poll is older than that interval, it polls right away.

Readiness

ToggldClient.create waits for the first snapshot, for up to sync.readyTimeoutMs (default 5000 ms). The Effect layer waits the same way. Then it resolves even if no snapshot has arrived, and synchronization keeps trying in the background.

If the first snapshot did not arrive in time, the SDK logs one warning with the last error. For example, with a wrong key:

[toggld] No flag snapshot after 5000 ms. Evaluations return your fallback values (PROVIDER_NOT_READY) until the first sync succeeds; sync keeps retrying in the background. Last error: The API rejected the SDK key (HTTP 401). Check that `sdkKey` is a valid, unrevoked key.

Until the first snapshot arrives:

  • Typed getters return your fallback value.
  • The details report reason: "ERROR" and errorCode: "PROVIDER_NOT_READY". The errorMessage names the last synchronization error, if there was one.
  • evaluateAll returns an empty array.

To wait for the first snapshot without a time limit, call ready():

const flags = await ToggldClient.create({ sdkKey, endpoint });
await flags.ready(); // resolves when the first snapshot arrives

ready() 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.

Sync mode on the hosted API. A change usually arrives as a stream push within moments. If the API fails to send a push, a retry runs every minute and sends it, about 10 to 70 seconds after the change. A connection that died without notice is found within 40 seconds. Then the SDK polls and reconnects, and the new connection sends the current snapshot. The hosted API can serve a polled snapshot from an edge cache for up to 60 seconds after a change. A polled snapshot never replaces a newer one that the stream already sent: each snapshot carries an ordered config version, and the SDK keeps the newer one. Without a working stream, plan for sync.pollIntervalMs, plus up to 60 seconds of cache age, plus request time. 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.

Sync 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, so the SDK always polls every sync.pollIntervalMs. 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,
  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 sync-mode 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:

ModeThroughputMedian latency
Remote (OFREP over HTTP)about 11,600 evaluations per second2.4 ms
Sync (in-process engine)about 1,090,000 evaluations per second27 µ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 runtimeStreamUpdates
Cloudflare Workers (the hosted API at api.toggld.io)Available, durable deliveryStream pushes, plus a poll every 5 minutes while the stream is healthy
Node.js (self-hosted)Available, best-effort deliveryStream pushes, plus a poll every 30 seconds

The main entry targets Node.js 22 or later. It uses Node.js APIs, such as node:fs for flag files. On runtimes that do not keep a process running, use the serverless client, which needs no Node.js APIs. See Choosing a runtime and mode.

Telemetry

In sync 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. File mode sends no telemetry. See Telemetry.

Evaluate from a file

File mode (mode: "file") reads the snapshot from a file instead of the network. Use it for local development, tests, and CI. A file-mode client needs no key and no network. The TOGGLD_FLAGS_FILE environment variable switches a client in any mode to file mode. See Flag files.

On this page