ShipEasy
Flags & ExperimentsGates

Quickstart

Create a gate, 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 gate. You'll create the gate, 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 · gate · ramp
01 · INSTALL

Install the SDK & log in

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

Create a gate at 0%

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

Gate the new code path

$if (await gate('checkout-v2', { userId })) { ... }
04 · RAMP

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

$shipeasy flags rollout checkout-v2 --percent 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 --kind 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 { shipeasy } from "@shipeasy/sdk/server";

await shipeasy({ apiKey: process.env.SHIPEASY_SERVER_KEY! });

Call this once during boot (server entry, root layout, or worker startup). The same shipeasy() call covers flags, configs, experiments, and translations — you do not need separate init for each.

2. Create the gate

shipeasy flags create checkout-v2 \
  --description "New checkout flow rewrite" \
  --rollout 0

--rollout 0 means "no one sees it yet." The gate exists, the SDK knows about it, but every call returns false. Safe to deploy.

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

3. Wrap the code path

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

export default async function CheckoutPage() {
  const userId = await getUserId();

  if (await gate("checkout-v2", { userId })) {
    return <CheckoutV2 />;
  }
  return <CheckoutV1 />;
}

Two things to note:

  • Always pass a stable userId. This is the bucketing key — same user, same answer, every request. If you pass a random id, the user re-buckets every request and you'll see flicker.
  • The call is synchronous in spirit. gate() does a hash-table lookup against the in-memory bundle. The await is there for the SDK's init guarantee, not for a network round-trip.

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 --percent 5

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

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

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

The same gate can be ramped from the dashboard with a slider, or from the API with a PATCH:

curl -X PATCH https://api.shipeasy.ai/v1/gates/checkout-v2 \
  -H "Authorization: Bearer $SHIPEASY_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rolloutPct": 25}'

5. Kill it if something goes wrong

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

shipeasy flags disable checkout-v2 --killswitch \
  --reason "Carts not finalising — PagerDuty #4912"

Every SDK serves false within the next poll (usually ~1s). Targeting rules and the rollout percentage are preserved — when you fix the bug, re-enabling restores the previous state exactly.

See Killswitches for the incident-grade variant that has no rollout knob and defaults to safe.

Where to next

On this page