
Product
Introducing Socket Scanning for VS Code Marketplace Extensions
Socket now scans VS Code extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.
launchflag
Advanced tools
One env key, one import, one line. Edge-safe, no dependencies, fails safe.
npx launchflag init --key lf_dev_… --url https://launchflag.dev
Using pnpm? Run pnpm dlx launchflag init instead of npx.
One command: it installs this package, writes LAUNCHFLAG_KEY into .env.local, downloads the agent skill and
configures the MCP server. Leave --key and --url off and it asks. --no-install skips the install step.
This package is both the SDK and the launchflag CLI. launchflag-mcp is a separate package you only need if
you want the MCP server running locally instead of hosted.
import { flag } from "launchflag";
if (await flag("checkout_v2", user.id)) return <CheckoutV2 />;
return <CheckoutV1 />;
Flags register themselves. Write the if, ship it dark, flip it on the dashboard.
The first time this code runs, checkout_v2 appears on your dashboard, off in dev, staging and prod, fail
closed. Until you turn it on, flag() returns false.
Rather do it by hand: npm i launchflag, then set LAUNCHFLAG_KEY (and LAUNCHFLAG_URL if you self-host)
yourself. Keys are on the dashboard Install page.
An async server component. React 19+, optional peer dependency.
import { Flag } from "launchflag/react";
<Flag name="checkout_v2" user={user.id} fallback={<CheckoutV1 />}><CheckoutV2 /></Flag>;
A multivariate flag serves one arm per user, stable forever and split by the weights you set on the dashboard.
import { variant } from "launchflag";
const arm = await variant("pricing_test", user.id); // "a" | "b" | null
null means the flag is off for this user, unknown, or boolean. Pass a third argument for a different fallback:
await variant("pricing_test", user.id, "a"). Same snapshot and cache as flag(), so it costs no extra request.
import { Variant } from "launchflag/react";
<Variant name="pricing_test" user={user.id} cases={{ a: <PriceA />, b: <PriceB /> }} fallback={<PriceA />} />;
LaunchFlag serves the arm and records nothing else. Measure it wherever you already measure things.
By default the SDK fetches one document, /api/config, and decides every flag in your process. One request per
ttlMs for the whole server, not per user, and it revalidates with If-None-Match so an unchanged config costs
nothing. First N targeting is the one exception: admission lives in our database, so those flags call /api/eval
and the answer is cached 60 seconds per user. Counts are buffered and posted to /api/counts every 10 seconds, or
on flush(). Pass mode: "remote" to evaluate server-side instead, one snapshot per user.
import { createLaunchFlag } from "launchflag";
const lf = createLaunchFlag({ key: process.env.LAUNCHFLAG_KEY!, baseUrl: "https://flags.acme.com", defaults: { checkout_v2: false } });
await lf.flag("checkout_v2", user.id);
await lf.flag("new_nav", user.id, true); // third argument: used only when nothing is known
// lf.variant("pricing_test", user.id) → the arm · lf.all(user.id) → every flag
// lf.why("checkout_v2", user.id) → { enabled, reason, variant } · lf.flush() · lf.clear()
If you have a server, it already knows the answers. Evaluate there and hand the result down: no key in the browser, no request from the browser, nothing to flicker.
// app/layout.tsx, a server component
import { snapshot } from "launchflag";
import { LaunchFlagProvider } from "launchflag/client";
export default async function Layout({ children }) {
const flags = await snapshot(["checkout_v2", "pricing_test"], user.id);
return <LaunchFlagProvider snapshot={flags}>{children}</LaunchFlagProvider>;
}
"use client";
import { useFlag, useVariant } from "launchflag/client";
export function Checkout() {
const on = useFlag("checkout_v2"); // second argument is the fallback, false by default
const arm = useVariant("pricing_test", "a");
return on ? <CheckoutV2 arm={arm} /> : <CheckoutV1 />;
}
snapshot(keys, user) returns { flags, variants }, plain JSON, evaluated from the same cached config as
flag(). Name the keys and only those reach the browser; leave keys off for every flag in the project.
With no provider mounted, useFlag returns its fallback and useVariant returns null. A missing provider
in one corner of an app is not worth a white screen.
This works anywhere React renders on a server: Next App Router and Pages Router, Remix, TanStack Start,
Astro React islands. The two async server components in launchflag/react are unchanged and still work.
The snapshot is plain data on purpose. Svelte, Vue and everything else do the same thing in a few lines, so there is no package from us to install.
// server: evaluate, then serialise it into the page however your framework passes data down
const flags = await snapshot(["checkout_v2"], user.id);
// browser: read it back
const flags = JSON.parse(document.getElementById("lf")!.textContent!);
if (flags.flags.checkout_v2) …
That is the whole contract: { flags: Record<string, boolean>, variants: Record<string, string | null> }.
A static build has no server at runtime, so it evaluates in the browser against a publishable key. Not at build time: flipping a flag would need a rebuild, and a kill switch you have to rebuild is not a kill switch.
import { createBrowserClient } from "launchflag/browser";
export const lf = createBrowserClient({
key: import.meta.env.VITE_LAUNCHFLAG_PUBLIC_KEY, // lfpub_prod_…, the only key that may be in browser code
user: currentUserId, // optional, the bucketing seed
defaults: { checkout_v2: false }, // what renders until the document arrives
});
lf.flag("checkout_v2"); // synchronous, never throws
lf.variant("pricing_test", "a");
lf.identify(user.id); // after sign-in, re-evaluates locally with no request
It fetches GET /api/config/public?key=… once on start, evaluates every flag locally with the same engine
the server runs, keeps the last good document in localStorage, and revalidates on a TTL (60s by default)
and whenever the tab comes back into view. Nothing here is awaited on the render path.
With React, hand it to the same provider and the hooks re-render when the document lands:
<LaunchFlagProvider source={lf}>{children}</LaunchFlagProvider>
createBrowserClient is also a getSnapshot / subscribe pair, which is all a Svelte store or a Vue ref
needs. npx launchflag init --public-key lfpub_prod_… writes the key with the prefix your bundler needs.
1. An exposed flag is published. Its key, whether it is on, and its rules all go into a document any visitor can read. Exposure is per flag and off by default: a flag appears in the public document only once you turn its browser exposure on in the dashboard. Do not expose a flag whose name gives away something you have not announced.
2. User targeting is spoofable in a browser. The user id comes from the page, so anyone can claim to be
anyone. Percentage rollout is fine, because bucketing is deterministic and the seed is not secret, and a
spoofer only moves themselves. Everything else is a request, not a fact: a client-side flag is a UI hint
and never an authorisation boundary. Gate the API, not the button. Allowlist flags cannot be exposed at
all, because the addresses would have to ship with them, and first_n flags evaluate to off in a browser
because admission lives in our database. Both stay server-side.
3. There is a first paint before the config arrives. On a first visit the browser paints before the
fetch resolves, so it renders defaults (and false for anything not in defaults). Then the document
lands and anything that changed re-renders: that is the flash people ship by accident. Three ways out, in
order of preference. Set defaults to the state you would rather show, which is usually the old one, so
the flash only hits users who are actually in the rollout. Return visits are already covered, because the
cached document answers before the first paint. Or, if a flag decides the whole page, hold the render on
await lf.ready and show your normal loading state, but never do that for a flag inside a list or a nav.
ttlMs and shared by every user, so a render costs zero requests. Concurrent calls share one fetch.timeoutMs.flag() returns defaults[key], then its third argument, else false. flag(), variant() and all() never throw.LAUNCHFLAG_KEY is a secret. Call flag() from Server Components, route handlers or middleware only. Importing launchflag from browser code resolves to a stub that throws and tells you which entry you wanted. For client components use launchflag/client, and with no server at all launchflag/browser.defaults, then the fallback you pass. Reads are synchronous and never throw, so nothing it does can break a render.FAQs
LaunchFlag feature flags client. One key, one function, edge-safe, fails safe.
The npm package launchflag receives a total of 5 weekly downloads. As such, launchflag popularity was classified as not popular.
We found that launchflag demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Product
Socket now scans VS Code extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.

Research
/Security News
Socket uncovered two malicious VS Code themes in a GlassWorm-linked cluster with thousands of installs across VS Code Marketplace and Open VSX.

Security News
/Company News
Capital One is partnering with Socket to proactively secure its open source supply chain.