Skip to main content
darkship docs
Open console

Start · 5 minutes

Quickstart: your first flag

Install an SDK, evaluate three flags of three different types, and see the evaluations arrive in the console. You need an account and one project; nothing else.

Updated Oct 28, 20264 min read Edit this page on GitHub (opens in a new tab)

Your language choice is remembered

Pick Node.js, Browser or Python in any snippet below. Every tab group on every docs page follows your choice.

Before you start#

You will use two kinds of key. Create them in the console under Environments and SDK keys; each key belongs to exactly one environment, so start with dev.

SDK key types. The full value is shown once, at creation.
KeyPrefixUse it inCan read rules?
Server keyds_srv_Node.js, Python, any backendYes (evaluates locally)
Client keyds_cli_Browsers, mobile appsNo (resolved values only)
Keep server keys out of the browser

A ds_srv_ key unlocks your whole ruleset. The browser SDK refuses to start with one, and we alert you if a server key is seen from a browser Origin. Load it from an environment variable or secret store.

Install the SDK#

Shellterminal
$ npm install @darkship/sdk

Initialise with your key#

Initialisation returns immediately; the SDK fetches the ruleset in the background. Calling waitForReady is optional but recommended at process start.

TypeScriptdarkship.ts
import { init } from '@darkship/sdk';

export const ds = init({
  sdkKey: process.env.DARKSHIP_SDK_KEY!, // ds_srv_dev_...
});

await ds.waitForReady({ timeout: 3000 }); // resolves even on timeout, see below
A timeout is not an error

If the ruleset can't be loaded in time, waitForReady resolves anyway and evaluations return your defaults with reason NOT_READY. Your app starts; flags catch up when the network does.

Evaluate three flags#

Every evaluation takes the flag key, a context, and a default. The default is returned if the flag is missing, has a different type, or the SDK isn't ready. The call never throws.

TypeScriptcheckout.ts
import { ds } from './darkship';

const ctx = { key: 'usr_marta', plan: 'pro', country: 'BR' };

// boolean
const newCheckout = ds.boolVariation('new-checkout-flow', ctx, false);

// string
const banner = ds.stringVariation('promo-banner-text', ctx, 'Welcome back');

// json (validated against the flag's JSON Schema when saved)
const limits = ds.jsonVariation('rate-limits', ctx, { rps: 10, burst: 20 });

console.log({ newCheckout, banner, limits });

Run it. With no flags created yet you will see your defaults:

Outputoutput
{ newCheckout: false, banner: 'Welcome back', limits: { rps: 10, burst: 20 } }

Create the flags in the console#

Open Flags → New flag and create new-checkout-flow (boolean), promo-banner-text (string) and rate-limits (JSON). Switch each one On in dev. The type is fixed at creation, so choose carefully; changing it later means a new flag.

Within seconds, your running process picks the change up; there is nothing to redeploy:

Outputoutput (a few seconds later)
{ newCheckout: true, banner: 'Free shipping this week', limits: { rps: 50, burst: 120 } }
Why it's fast

Changes are published as a versioned snapshot and pushed to server SDKs over a stream (on by default; polling every 30 s is only the fallback), so they propagate in under 5 seconds at p95. See snapshot propagation.

Verify in the console#

The SDK counts evaluations per flag and variation in one-minute windows and flushes them in the background. Open the flag and check Usage; after about a minute you should see something like this:

devSDK connected
new-checkout-flowon1,842
promo-banner-textfree-shipping1,842
rate-limitsburst1,842

Only counts are sent: environment (derived from your key), flag key, variation name, window and count. The context you pass to boolVariation never leaves your process in server mode, and is never stored for client mode. Details.

Nothing showing up?

Check, in order: the key is for the same environment you toggled; the flag type matches the accessor you called (a wrong type returns your default); and the process stays alive for at least one flush interval, or call await ds.close() to flush on exit.

Where to go next#

Was this helpful?