Shipeasy

Advanced use cases

Five cross-cutting patterns that combine flags, configs, killswitches, experiments, metrics, alerts and feedback into one workflow — the things you can only do when it's all one platform.

GuideOn this page · 6 min readUpdated · July 5, 2026Works with · Whole platform · SDK · CLI · Admin API

Every primitive on its own is documented in its own section. This page is about the seams — the workflows that only exist because flags, configs, killswitches, experiments, metrics, alerts and feedback share one SDK, one API and one project. Each pattern below states the problem, the approach, and which primitives it wires together. Follow the links for the step-by-step version.

Roll a feature out to new tenants automatically

Problem. You're rolling a feature out tenant by tenant, but your tenant list is never static — new accounts sign up every day, and you don't want to redeploy or hand-edit a targeting rule each time one does.

Approach. Create one killswitch for the feature. A killswitch carries a map of named switches — per-key boolean overrides layered on top of the global state. Have your provisioning code call the Admin API to insert a tenant:<id> switch the moment a tenant is created, then flip that one switch to grant or revoke access — no code change, no deploy, one lever per tenant.

import { configure, Client } from "@shipeasy/sdk/server";

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

export async function handler(tenantId: string) {
  const flags = new Client({ user_id: tenantId });
  // resolves the tenant's named switch, falling back to the global state
  if (flags.getKillswitch("new-checkout", `tenant:${tenantId}`)) {
    return newCheckout(tenantId);
  }
  return legacyCheckout(tenantId);
}
# your onboarding job runs this as each tenant is provisioned
shipeasy killswitch toggle new-checkout --switch tenant:acme --on --env prod

Combines: killswitch switches + the Admin API driven from your own provisioning code.

Change plan entitlements without a redeploy

Problem. A customer upgrades, downgrades, or negotiates a custom limit, and you want the new entitlements — seat count, API quota, which features are unlocked — to take effect immediately, without shipping code or running a migration.

Approach. Model entitlements as a typed dynamic config rather than a flag. Store the limits and feature set as the config's value, and target it by a plan (or tenant) attribute. Your code reads one getConfig(); a plan change is a config edit — or just a change to the customer's attribute — applied through the dashboard, CLI or Admin API and live on the next poll.

const flags = new Client({ user_id, attributes: { plan: "business" } });
const limits = flags.getConfig("entitlements"); // { seats: 25, apiRpm: 600, export: true }

Combines: dynamic configs (typed values + targeting) + attributes + the Admin API for programmatic upgrades.

Ramp a feature that halts itself when a guardrail regresses

Problem. You want to ramp a risky change from 1% to 100%, but not babysit a dashboard the whole time. If it drives error rate or latency the wrong way, it should stop — or at least page someone — before it reaches everyone.

Approach. Put the feature behind a gate and ramp its rollout percentage. Define the thing you're afraid of breaking as a guardrail metric, and attach a threshold alert to it — "checkout errors > 50 in the last hour". The alert is evaluated server-side by cron and files a ticket into your feedback queue the instant the guardrail breaches, while you keep the same feature's killswitch armed as the break-glass to cut it to zero without redeploying.

Combines: gate rollout + a metric + a threshold alert (auto-files a ticket) + a killswitch for break-glass.

Alerts need no SDK call

Alert rules are evaluated by a cron on the edge — you only define the rule and the metric. Nothing to poll, nothing to wire into the hot path.

Measure what a whole quarter of shipping actually added

Problem. Every experiment you ran this quarter showed a small lift in isolation. But did they compound, or did later wins quietly cannibalise earlier ones? A stack of individual p-values can't answer that.

Approach. Run the quarter's experiments inside a single universe with a holdout. Because holdouts live on the universe — not the individual experiment — the held-out slice never sees any of the changes. Comparing the holdout against everyone else measures the cumulative impact of everything you shipped, end to end. Run the experiments as mutually-exclusive within that universe so a single user never lands in two at once and the attribution stays clean.

Combines: a universe + a universe-level holdout + mutually-exclusive experiments + your success metrics.

Turn a spiking exception into an opened PR

Problem. A handled error that used to fire twice a day starts firing twice a minute after a deploy. You want it noticed, triaged and — where possible — fixed, without a human watching log volume.

Approach. Report the error through see() instead of a bare console.error, so it becomes a tracked error with a frequency the backend counts. Point a threshold alert at that error's rate; when it crosses the line the alert files a bug into the same ops queue as everything else. From there an AI trigger — Claude, Copilot, Cursor or Jules — picks the ticket up and opens a pull request against it. The loop from caught exception to proposed fix runs without anyone paging themselves.

Combines: see() error reporting → tracked-error frequency → a threshold alert (files the ticket) → the ops queue → an AI trigger that opens the PR.

Where to go next

These patterns are deliberately composed from primitives you can read about individually. If one is close to what you need, start from its section links above; if you want the smaller, single-primitive recipes, the Flags & Experiments case studies are the next level down.

Was this page helpful?
✎ Edit this page

On this page