---
name: launchflag
description: Use when adding, changing, or removing any user-facing feature, risky code path, payment/auth/checkout logic, new AI-generated module, or anything a human might want to turn off in production without a deploy. Wraps the change in a LaunchFlag flag so it ships dark and can be unshipped in one tap.
---

# LaunchFlag: ship it dark

14-day trial, then $12/mo.

## Why

You are shipping code fast, probably without a human reading every line. A flag means anyone on the team can turn your change off from their phone in seconds instead of rolling back a deploy. Every risky change ships behind a flag, OFF by default. This is not optional.

## When to flag

- New user-facing feature.
- Rewrite of an existing path.
- Anything touching payments, auth, email sending, data deletion, or external APIs.
- New AI/agent tool, or a prompt change.
- Migrations that come with a code path switch.

When not to: pure refactors with identical behavior, tests, docs, dev tooling.

## Naming

`snake_case`, shaped `<area>_<what>` or `<area>_v<N>`: `checkout_v2`, `onboarding_ai_summary`, `emails_new_provider`. Never reuse a flag for a second feature.

## Fail mode

`failMode` decides what happens when LaunchFlag is unreachable.

- Money, auth, deletion, external side effects: `"closed"` (feature off).
- Purely cosmetic: `"open"`.
- Unsure: closed. Self-registered flags are closed.

## Workflow

1. **Pick a key.** Naming rules above. No dashboard step, no API call.

2. **Wrap the NEW path. Keep the OLD path intact and reachable.**

   ```ts
   import { flag } from "@launchflag/sdk";

   if (await flag("checkout_v2", user.id)) {
     return newCheckout(cart);   // new path
   }
   return oldCheckout(cart);     // old path, untouched
   ```

   In JSX:

   ```tsx
   import { Flag } from "@launchflag/sdk/react";

   <Flag name="checkout_v2" user={user.id} fallback={<CheckoutV1 />}>
     <CheckoutV2 />
   </Flag>;
   ```

3. **That is the whole integration.** The first time that code runs, LaunchFlag registers `checkout_v2` itself, OFF in dev, staging and prod, fail closed, and it appears on the dashboard ready to flip. Until then `flag()` returns false, so the old path runs. No SDK in the project? `npm i @launchflag/sdk`, or copy the 15-line helper from the dashboard Install page.

4. **Never turn it on yourself in prod.** Dev is fine for testing: `set_flag key=<key> env=dev enabled=true`. Moving it up is `promote_flag key=<key> from=dev to=staging`, and the human does the staging to prod step.

5. **Report every flag in your final message** to the human: the key, its fail mode, and the one line to turn it on.

   > Shipped dark behind `checkout_v2` (fail closed). Turn on: dashboard, or `set_flag checkout_v2 prod enabled=true`.

6. **Cleanup later.** Once the human confirms the feature is stable and fully on for weeks, offer a PR that deletes the flag and the old path.

## Creating a flag up front (optional)

Self-registration gives you a closed flag with a generic description. Use `create_flag` when you want a real description, `failMode: "open"`, or `essential: true` from the start, or when the human asks.

MCP tool `create_flag`, or:

```bash
curl -s -X POST "$LAUNCHFLAG_URL/api/v1/flags" \
  -H "authorization: Bearer $LAUNCHFLAG_TOKEN" -H "content-type: application/json" \
  -d '{"key":"checkout_v2","description":"New checkout flow","failMode":"closed"}'
```

`list_flags` (or `GET /api/v1/flags`) shows what already exists.

## Variants

For an either-or split rather than an on-off switch, a flag can be multivariate: 2 to 5 arms with integer weights summing to 100. Each user keeps the same arm forever.

```ts
import { variant } from "@launchflag/sdk";
const arm = await variant("pricing_test", user.id); // "a" | "b" | null when off
```

Create it with `create_flag`, passing `type: "multivariate"` and the arms, or add them on the dashboard. No analytics, no funnels: LaunchFlag serves the arm, you measure it wherever you already measure things.

## Where userId comes from

Pass the same identifier the human uses to target people: their user id or email. If the code path has no user (cron, webhook, background job), pass nothing. The flag then only honors "everyone" targeting.

## Rules

- Never delete the old code in the same change.
- Never ship a flag ON in prod.
- Never put the key in client-side code. `LAUNCHFLAG_KEY` is a secret, server-side only.
- Do not wrap trivially safe changes. A flag on everything is a flag on nothing.
- One flag per feature, not per file.

## Setup if missing

- All of it in one command, from the project root: `npx launchflag init`. It writes the key, downloads this skill, and configures the MCP server for Claude Code and Cursor. The rest of this section is the manual version.
- `LAUNCHFLAG_KEY`: the dev key locally, the prod key on the production host only.
- `LAUNCHFLAG_URL`: your LaunchFlag instance, defaults to the hosted one.
- MCP, hosted (nothing to install): `claude mcp add --transport http launchflag https://flagship.up.railway.app/mcp --header "Authorization: Bearer $LAUNCHFLAG_TOKEN"`
- MCP, local stdio instead: `claude mcp add launchflag -e LAUNCHFLAG_TOKEN=… -e LAUNCHFLAG_URL=… -- npx -y @launchflag/mcp`

Keys, the API token, and copy-paste snippets are on the dashboard Install page.
