npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

switchbox-js

v0.6.0

Published

Feature flag SDK for the browser. Zero dependencies.

Readme

switchbox-js

Feature flags served from a CDN. Zero dependencies. Sub-millisecond evaluation.

npm bundle size License

What is this?

Switchbox is a feature flag SDK that reads configs from a CDN instead of an API server. Flag configs are static JSON files on the edge — your app fetches them directly. Rules and rollouts are evaluated locally in the browser, not on a server.

This is the core browser SDK. For React hooks and components, see @switchbox/react.

Install

npm install switchbox-js

Quick Start

import { Switchbox } from 'switchbox-js';

const client = await Switchbox.create({ sdkKey: 'your-sdk-key-from-dashboard' });

if (await client.enabled('new_checkout', { user_id: '42' })) {
  showNewCheckout();
}

client.destroy();

Evaluation is async. enabled(), getValue(), and getAllFlags() return promises — rollout bucketing uses the Web Crypto subtle.digest API, which is async. Always await them.

Features

  • CDN-first — fetches flag configs from static JSON on a CDN, served from the edge, never from our API
  • Zero dependencies — browser APIs only (fetch, Web Crypto); npm install switchbox-js pulls in nothing else
  • Sub-millisecond evaluation — rules and rollouts evaluated locally in the browser, no network call per flag check
  • Background polling — syncs configs every 30 seconds (configurable)
  • Offline resilient — keeps working on the last fetched config if the CDN is unreachable
  • Live updates — subscribe to config changes so a flag toggle reaches your UI within one poll interval
  • Tiny — a few KB, tree-shakeable ESM

Usage

Boolean flags

const client = await Switchbox.create({ sdkKey: 'your-sdk-key-from-dashboard' });

if (await client.enabled('dark_mode')) {
  enableDarkMode();
}

String / number / JSON flags

const version = await client.getValue('search_algorithm', { user_id: '42' }, 'v1');

const maxResults = await client.getValue('max_search_results', { user_id: '42' }, 10);

The third argument is the default returned when the flag doesn't exist.

All flags at once

const flags = await client.getAllFlags({ user_id: '42' });
// { dark_mode: true, search_algorithm: 'v2', max_search_results: 50 }

Targeting rules

Pass a user object with attributes you want to target on. Rules are configured in the dashboard.

const user = {
  user_id: '42',
  email: '[email protected]',
  plan: 'enterprise',
  age: '30',
};

// Flag with rule: email ends_with "@company.com"
await client.enabled('internal_tools', user); // true

// Flag with rule: plan equals "enterprise"
await client.enabled('advanced_analytics', user); // true

// Flag with rule: plan in_list ["pro", "enterprise"]
await client.enabled('export_csv', user); // true

Supported operators: equals, not_equals, contains, ends_with, in_list, gt, lt.

Rules use OR logic — if any rule group matches, the flag is on for that user.

Percentage rollouts

Rollouts use deterministic hashing (sha256(user_id:flag_key) % 100). The same user always gets the same result for a given flag — no flickering between renders — and the same bucket in every SDK language (a user who's in for a flag in the JS SDK is also in for it in the Python SDK).

// Flag with rollout_pct=25 — 25% of users get this flag
await client.enabled('new_onboarding', { user_id: '42' }); // deterministic true/false

A user_id (or id) key is required in the user object for percentage rollouts.

Offline / fail-safe behavior

If the CDN is unreachable, the SDK keeps using the last successfully fetched config. Your flags keep working.

If the SDK has never successfully fetched a config (e.g. the CDN is down on first load), enabled() returns false and getValue() returns the default you pass in. Fetch errors are never thrown — they're routed to the onError callback instead, so a flag check never crashes your app.

Blocking vs. non-blocking startup

Switchbox.create(options) awaits the first fetch, so the client already sees live config when it resolves — the recommended entry point. The trade-off is that it waits on the network before resolving.

To start without waiting, construct with new Switchbox(options) and call init() without awaiting it. The client fetches in the background; flag checks fall back to your supplied defaults until the first config lands:

const client = new Switchbox({ sdkKey: '...' });
client.init(); // fetches in the background — not awaited
// checks use defaults until the first config arrives
if (await client.enabled('new_checkout', { user_id: '42' })) {
  // ...
}

(The Python SDK makes the same choice with a block_on_init flag: block_on_init=True blocks on the first fetch, False fetches in the background.)

Live updates

onConfigChange(callback) fires whenever the polled config version changes, and returns an unsubscribe function. This is how @switchbox/react's hooks re-render on a flag toggle; use it directly for non-React live updates.

const unsubscribe = client.onConfigChange(() => {
  rerenderFeatureToggles();
});
// later: unsubscribe();

Analytics / exposure tracking

Switchbox doesn't track flag evaluations. Wire evaluations into your own analytics with onEvaluation:

const client = await Switchbox.create({
  sdkKey: 'your-sdk-key-from-dashboard',
  onEvaluation: (flagKey, result, user) => {
    analytics.track('flag_evaluated', { flag: flagKey, result });
  },
});

Configuration

const client = await Switchbox.create({
  sdkKey: 'your-sdk-key-from-dashboard', // required — get from the Environments tab
  cdnBaseUrl: 'https://cdn.switchbox.dev', // override the CDN origin (default shown)
  pollInterval: 30, // seconds between background polls (default: 30)
  onError: (error) => console.error(error), // called on fetch/parse errors (default: none)
  onEvaluation: (flagKey, result, user) => {}, // called on every evaluation (default: none)
});

| Option | Type | Default | Description | |----------------|---------------------------------------------------|-------------------------------|----------------------------------------------------| | sdkKey | string | — | SDK key from the environment in the dashboard | | cdnBaseUrl | string | https://cdn.switchbox.dev | CDN origin; the URL is {cdnBaseUrl}/{sdkKey}/flags.json | | pollInterval | number | 30 | Seconds between background config refreshes | | onError | (error: Error) => void | undefined | Callback invoked when a fetch or parse fails | | onEvaluation | (flagKey, result, user?) => void | undefined | Callback invoked on every flag evaluation |

How It Works

┌──────────┐       ┌──────────┐       ┌─────────────┐
│Dashboard │──────>│ API      │──────>│  Postgres   │
│          │ HTTP  │ (Fly.io) │  SQL  │  (Neon)     │
└──────────┘       └────┬─────┘       └─────────────┘
                        │
                        │ publish on every change
                        v
                 ┌─────────────┐       ┌──────────────┐
                 │CDN Publisher│──────>│Cloudflare R2 │
                 │             │  PUT  │(static JSON) │
                 └─────────────┘       └──────┬───────┘
                                              │
                                              │ HTTP GET (SDK polls)
                                              v
                                       ┌──────────────┐
                                       │  Your App    │
                                       │  (this SDK)  │
                                       └──────────────┘
  1. You create and toggle flags in the dashboard
  2. On every change, the API generates a static JSON file and uploads it to the CDN
  3. This SDK polls that JSON file from the edge every 30 seconds
  4. Flag evaluation (rules, rollouts) happens locally — no network call per flag check

The API server is only in the write path. All read traffic goes to the CDN.

API Reference

Switchbox.create(options): Promise<Switchbox>

Create a client and await the first config fetch in one call — the recommended entry point. See Configuration for the options.

new Switchbox(options) + client.init(): Promise<void>

Construct without fetching, then start polling with init(). Await init() to block on the first fetch, or leave it un-awaited to fetch in the background. See Blocking vs. non-blocking startup.

client.enabled(flagKey, user?): Promise<boolean>

Check if a boolean flag is enabled. Resolves to false if the flag doesn't exist.

| Parameter | Type | Description | |-----------|----------------------------|-------------------------------------| | flagKey | string | The flag key to check | | user | UserContext \| undefined | User context for targeting/rollouts |

client.getValue(flagKey, user?, defaultValue?): Promise<any>

Get the resolved value of any flag type (string, number, JSON). Resolves to defaultValue if the flag doesn't exist.

| Parameter | Type | Description | |----------------|----------------------------|--------------------------------------| | flagKey | string | The flag key to check | | user | UserContext \| undefined | User context for targeting/rollouts | | defaultValue | any | Value returned if the flag is absent |

client.getAllFlags(user?): Promise<Record<string, any>>

Get all flag values resolved for a user. Resolves to an empty object if no config is available.

client.onConfigChange(callback): () => void

Subscribe to config-version changes. Returns an unsubscribe function. See Live updates.

client.destroy(): void

Stop background polling and clear subscribers. Call this when the client is no longer needed.

Contributing

git clone https://github.com/ignat14/switchbox-sdk-js.git
cd switchbox-sdk-js
pnpm install
pnpm -r build
pnpm test

License

MIT