Dashboard
JavaScript (server)

Choosing a runtime and mode

Which client to use on Node.js servers, Cloudflare Workers, Vercel, AWS Lambda, and other runtimes, and what each mode costs.

@toggld/js evaluates flags in four ways: three modes of ToggldClient, and the serverless client. Pick one from the runtime that your code runs on.

Sync mode (default)Serverless client (on-demand)Remote modeFile mode
ImportToggldClient from @toggld/jscreateServerlessClient from @toggld/js/serverlessToggldClient from @toggld/js, with mode: "remote"ToggldClient from @toggld/js, with mode: "file"
How it evaluatesIn your process, from a copy of the flag rules that a stream and a poll keep currentIn your process, from a copy in memory that requests refresh on demand when it gets oldOne HTTP request to the API per evaluationIn your process, from a flag file on disk, watched for edits
NeedsA long-lived processMemory that lasts across requests (an isolate or a warm process)NothingA file system. No network.
Billable requestsNoneNoneOne per evaluationNone
CredentialSecret key (ffk_)Secret key (ffk_)Secret key or public token (ffp_)None

File mode is for local development, tests, CI, and pinned rulesets, not for following changes that you save in Toggld. See Flag files.

What each mode costs

Toggld bills remote evaluations: each request to an OFREP evaluation endpoint counts. Definition sync (GET /sync/v1/flags and the /sync/v1/stream WebSocket) and telemetry are fair use and are not billed. Sync mode and the serverless client download definitions and evaluate in your code, so their evaluations are not billable requests. File mode makes no requests. See Plans and limits.

Definition sync still uses your rate limit: one snapshot request per poll in sync mode, and one per refresh in the serverless client. A refresh of an unchanged snapshot is a conditional request that the API answers with 304 Not Modified and no body.

How fresh each mode is

How long after you save a flag change does your code see it? The typical times come from production benchmarks against the hosted API from one location in the western United States. Your times depend on where your code runs.

ModeHow a change arrivesTypicalWorst case
Sync mode (@toggld/js)Pushed over the streamAbout 0.6 secondsAbout 70 seconds, when a push fails and the retry sends it. See How fast changes arrive.
Browser SDK (@toggld/browser)Pushed over the evaluation streamAbout 0.6 secondsThe next poll or refresh, if the stream is down
Serverless client (@toggld/js/serverless)Refreshed when the snapshot is older than maxAgeMsAbout 26 seconds with the defaults (5 changes: 6 to 50 seconds)About maxAgeMs plus 60 seconds: 90 seconds with the defaults
Remote modeRead from the API on each evaluationAbout 0.5 seconds near where the change was saved60 seconds, in other locations
File modeRead when the file changes on disk. Changes saved in Toggld arrive only when you pull the file again.Moments after the file is written30 seconds, if the file watcher misses the edit

The serverless client needs a request to notice that its snapshot is old. An isolate or a process that receives no requests sees the change on its next request. To get changes sooner, lower maxAgeMs. Each refresh of an unchanged snapshot is a small 304 Not Modified response, and it is not a billable request.

  • Sync mode gets most changes as a stream push within moments, and polls as a safety net. See How fast changes arrive.
  • The serverless client uses a snapshot for up to maxAgeMs (default 30 seconds), then refreshes it on the next request. The API can serve a snapshot from its edge cache for up to 60 seconds after a change. So a change reaches an isolate or a process within about maxAgeMs plus 60 seconds, after it receives a request. Each isolate or process holds its own snapshot, so two of them can briefly disagree. See Freshness.
  • Remote mode reads the API on every evaluation. The same edge cache can serve flag data for up to 60 seconds after a change.
  • File mode follows the file, not Toggld. It adopts an edit as soon as its file watcher sees it, and re-reads the file every 30 seconds as a safety net.

Runtimes

"Tested" means that the Toggld test suite runs the client in that runtime. "Expected" means that the runtime provides what the client needs, but Toggld does not test it yet.

RuntimeUseStatus
Node.js servers, containers, Fly.io, Render, ECS, Kubernetes@toggld/js in sync mode (stream and poll)Tested on Node.js 22 and later
Bun and Deno servers@toggld/js in sync modeExpected
Cloudflare WorkersThe serverless client with ctx.waitUntilTested in workerd, without nodejs_compat
Vercel Edge Functions, Next.js middlewareThe serverless client with waitUntil from @vercel/functions, or the middleware eventExpected
Vercel Node.js functions, Netlify Functions, AWS Lambda (including Lambda@Edge)The serverless client without a context: forRequest()Expected. The no-context mode is tested on Node.js, not on these platforms.
Netlify Edge Functions, Deno DeployThe serverless clientExpected
Fastly ComputeRemote modeExpected
CloudFront FunctionsNot supported. They cannot make network requests.—
Browsers@toggld/browser or @toggld/react, with a public tokenTested

Never put a secret key (ffk_) in browser code. Secret keys can read your targeting rules.

Why not sync mode everywhere

Sync mode keeps a WebSocket, timers, and a background poll open for the life of the process.

  • Cloudflare Workers and Vercel Edge run code only while a request is in progress, and the I/O of each request belongs to that request. They cannot keep a stream or a poll open. ToggldClient.create in sync mode throws a TypeError there that points to the serverless client.
  • AWS Lambda, Vercel Node.js functions, and Netlify Functions freeze the process as soon as the handler returns. While the process is frozen, the stream and the poll stop, so after a freeze an evaluation can use a snapshot of any age. On Lambda, sync mode logs one warning. The serverless client checks the snapshot age in each invocation instead.
  • Fastly Compute starts a fresh instance for each request and keeps no memory between requests. A serverless client would download the snapshot on every request, so remote mode costs less there.

Examples

Cloudflare Workers

src/index.ts
import { createServerlessClient, type ServerlessClient } from "@toggld/js/serverless";

let flags: ServerlessClient | undefined;

export default {
  async fetch(request, env, ctx): Promise<Response> {
    // One client per isolate. Create it on the first request, where `env` is available.
    flags ??= createServerlessClient({ sdkKey: env.TOGGLD_SDK_KEY, endpoint: "https://api.toggld.io" });
    const f = flags.forRequest(ctx);
    const on = await f.getBooleanValue("new-checkout", false, { targetingKey: "user-123" });
    return new Response(on ? "new checkout" : "old checkout");
  },
} satisfies ExportedHandler<Env>;

AWS Lambda

handler.ts
import { createServerlessClient } from "@toggld/js/serverless";

// Module scope: the client and its snapshot survive between warm invocations.
const flags = createServerlessClient({
  sdkKey: process.env.TOGGLD_SDK_KEY!,
  endpoint: process.env.TOGGLD_API_URL!,
});

export const handler = async (event: { userId: string }) => {
  // No context: a stale snapshot is refreshed inside this invocation.
  const f = flags.forRequest();
  const on = await f.getBooleanValue("new-checkout", false, { targetingKey: event.userId });
  return { statusCode: 200, body: on ? "new checkout" : "old checkout" };
};

Node.js server

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!,
});

See Sync mode and Serverless client for the details of each.

Effect

Both clients have an Effect API that provides the same ToggldClient service, so Effect program code is the same on every runtime. Pick the layer:

RuntimeLayer
Long-lived serverslayer from @toggld/js/effect
Cloudflare Workers, Vercellayer from @toggld/js/serverless/effect, with withWaitUntil(ctx) per request
AWS Lambda, Node.js functionslayer from @toggld/js/serverless/effect, without withWaitUntil

See Serverless client: Effect API.

On this page