Shipeasy
Flags & ExperimentsFeature flags

Quickstart

Create a feature flag, wrap your code, ramp from 0% to 100% — end to end in five minutes.

TutorialOn this page · 5 min readUpdated · May 15, 2026Works with · Server SDK · Browser SDK · CLI

This walks you through shipping a real feature behind a feature flag. You'll create the feature flag, wrap a code path, and ramp it from 0% → 5% → 25% → 100%. By the end the feature is live for everyone, with no redeploys between ramp steps.

The 5-minute path

install · create · flag · ramp
01 · INSTALL

Install the SDK & log in

$npm install @shipeasy/sdk && npx shipeasy login
02 · CREATE

Create a feature flag at 0%

$shipeasy flags create checkout-v2 --rollout 0
03 · WRAP

Put the new code path behind a feature flag

$const flags = new Client(currentUser); if (flags.getFlag('checkout-v2')) { ... }
04 · RAMP

Bump to 5%, then 25%, then 100%

$shipeasy flags rollout checkout-v2 100

Prerequisites

  • A Shipeasy project. The free tier covers everything in this walkthrough.
  • A server SDK key for the environment you're deploying to:
shipeasy keys create --type server
  • The SDK in your project:
$npm install @shipeasy/sdk

1. Initialise once at boot

The SDK loads the flag bundle into memory and refreshes it in the background. No fetch on the hot path, no per-request latency. Configure once, use everywhere.

src/lib/shipeasy.ts
import { configure } from "@shipeasy/sdk/server";

configure({
  apiKey: process.env.SHIPEASY_SERVER_KEY ?? "",
  attributes: (u) => ({ user_id: u.id, plan: u.plan }),
});

Call this once during boot (server entry, root layout, or worker startup). The same configure() call covers flags, configs, experiments, and translations — you do not need separate init for each. The optional attributes transform maps your user object onto the Shipeasy attribute map so that every bound Client you construct evaluates against the right context.

2. Create the feature flag

shipeasy flags create checkout-v2 --rollout 0

--rollout 0 means "no one sees it yet." The feature flag exists, the SDK knows about it, but every call returns false. Safe to deploy. (A description field exists on the feature flag row but the CLI's flags create doesn't accept a --description flag today — add the description from the dashboard after creating.)

Alternatively, create it in the dashboard: Flags → New feature flag → Save.

3. Wrap the code path

app/checkout/page.tsx
import { Client } from "@shipeasy/sdk/server";

export default async function CheckoutPage() {
  const flags = new Client(await getCurrentUser());

  if (flags.getFlag("checkout-v2")) {
    return <CheckoutV2 />;
  }
  return <CheckoutV1 />;
}
flags = shipeasy.Client(current_user)
if flags.get_flag("checkout-v2"):
    return checkout_v2()
return checkout_v1()
flags := shipeasy.NewClient(currentUser)
if flags.GetFlag("checkout-v2") {
    return checkoutV2()
}
return checkoutV1()
flags = Shipeasy::Client.new(current_user)
if flags.get_flag("checkout-v2")
  render CheckoutV2
else
  render CheckoutV1
end
Client flags = new Client(currentUser);
if (flags.getFlag("checkout-v2")) {
    return checkoutV2();
}
return checkoutV1();
val flags = Client(currentUser)
if (flags.getFlag("checkout-v2")) {
    checkoutV2()
} else {
    checkoutV1()
}
$flags = new Shipeasy\Client($currentUser);
if ($flags->getFlag('checkout-v2')) {
    return checkoutV2();
}
return checkoutV1();
let flags = try Client(currentUser)
if await flags.getFlag("checkout-v2") {
    return checkoutV2()
}
return checkoutV1()

Two things to note:

  • Bind the client to your user once. The attributes transform you passed to configure() resolves a stable user_id — the bucketing key. Same user, same answer, every request.
  • The call is synchronous. flags.getFlag() does a hash-table lookup against the in-memory bundle — no network round-trip per read. (In Swift the getters are async because the engine is an actor.)

Deploy this. With --rollout 0, every user sees CheckoutV1. The new path is shipped but dark.

4. Ramp gradually

When you're ready to start exposing real users:

# Day 1 — 5% of all users
shipeasy flags rollout checkout-v2 5

# Day 3 — 25%, after metrics look fine
shipeasy flags rollout checkout-v2 25

# Day 5 — full launch
shipeasy flags rollout checkout-v2 100

Each ramp step propagates to every SDK in under a second. No redeploy, no env var change.

The same feature flag can be ramped from the dashboard with a slider, or via the Admin API. Resolve the feature flag's id, then PATCH:

# Resolve `checkout-v2` → its UUID
GATE_ID=$(curl -sS \
  -H "Authorization: Bearer $SHIPEASY_ADMIN_KEY" \
  "https://shipeasy.ai/api/admin/gates?name=checkout-v2" | jq -r '.data[0].id')

curl -X PATCH "https://shipeasy.ai/api/admin/gates/$GATE_ID" \
  -H "Authorization: Bearer $SHIPEASY_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rolloutPct": 2500}'   # rolloutPct is basis points: 2500 = 25%

5. Kill it if something goes wrong

If the new path breaks at 25%, flip the killswitch:

shipeasy flags disable checkout-v2

Every SDK serves false within the next poll (default 30s on Free, 60s on Team). Targeting rules and the rollout percentage are preserved on the feature flag — when you fix the bug, shipeasy flags enable checkout-v2 restores the previous state exactly.

For incident-grade flips that propagate faster and are a dedicated control surface, use a killswitch instead.

Where to next

Was this page helpful?
✎ Edit this page

On this page