rollfuse
← Back to tutorials

Roll out a flag to 10% of users and increase it safely

This tutorial schedules a rollout that starts at 10% exposure and increases from there, explains the bucketing math that keeps a given user's outcome stable across stages, and shows how to intervene if a stage doesn't look right.

1. Schedule a rollout starting at 10%

A rollout is attached to an environment's flag config, not to the flag itself, so create it with POST /v1/environment-flag-configs/{environment_flag_config_id}/rollouts. environment_flag_config_id is the id returned when you PUT the flag's config for that environment (see "Creating a feature flag"). The config must have no manually-authored rules and no other scheduled or active rollout when you create this one.

A rollout is a list of stages, each with a target_percentage and an activate_at time. Percentages must strictly increase and the last stage must reach 100:

const res = await fetch(
  `https://api.rollfuse.com/v1/environment-flag-configs/${environmentFlagConfigId}/rollouts`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      target_variation_id: onVariationId,
      stages: [
        { target_percentage: 10, activate_at: "2026-09-06T12:00:00Z" },
        { target_percentage: 50, activate_at: "2026-09-08T12:00:00Z" },
        { target_percentage: 100, activate_at: "2026-09-10T12:00:00Z" },
      ],
    }),
  }
);
const rollout = await res.json();
console.log(rollout.id, rollout.state); // "scheduled"

The rollout starts in the scheduled state and becomes active as its stages fire. You can check its current state, and which stage was last applied, with GET /v1/rollouts/{rollout_id}:

const check = await fetch(`https://api.rollfuse.com/v1/rollouts/${rollout.id}`, {
  headers: { Authorization: `Bearer ${token}` },
});
const current = await check.json();
console.log(current.state, current.last_applied_stage_position);

2. Why the same user stays on the same side as the percentage rises

@rollfuse/sdk-js exports a bucket() function that is the same deterministic function the platform uses to decide who's included at a given rollout percentage:

import { bucket, BUCKET_MODULUS } from "@rollfuse/sdk-js";

const b = bucket("new-checkout-flow", "user-42");
console.log(b, BUCKET_MODULUS); // e.g. 3821 10000

bucket(flagKey, subjectKey) hashes flagKey + ":" + subjectKey with FNV-1a and reduces it modulo BUCKET_MODULUS (10000), giving 0.01% granularity. A stage at 10% includes every subject whose bucket value is below 1000 out of 10000. Because the hash depends only on the flag key and the subject key, a given user's bucket value never changes for that flag across stages. That's what keeps a rollout from flip-flopping users in and out as it advances: going from a 10% stage to a 50% stage only ever adds users, it never removes anyone already included.

function isIncludedAtPercentage(flagKey, subjectKey, percentage) {
  return bucket(flagKey, subjectKey) < (percentage / 100) * BUCKET_MODULUS;
}

isIncludedAtPercentage("new-checkout-flow", "user-42", 10); // false, if b = 3821
isIncludedAtPercentage("new-checkout-flow", "user-42", 50); // true, since 3821 < 5000

3. Intervene mid-rollout if something looks wrong

You don't edit a stage's percentage after the fact; instead, control the rollout's lifecycle directly:

  • POST /v1/rollouts/{rollout_id}/pause freezes it at its current percentage.
  • POST /v1/rollouts/{rollout_id}/resume continues a paused rollout.
  • POST /v1/rollouts/{rollout_id}/abort ends it before it reaches 100%.
await fetch(`https://api.rollfuse.com/v1/rollouts/${rollout.id}/pause`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
});

If you want this to happen automatically instead of by hand, see the automatic guardrail rollback tutorial: attaching a guardrail to this same rollout lets a breaching metric pause or abort it for you.

4. Confirm it from application code

Evaluation from @rollfuse/sdk-js reflects the rollout's current stage automatically; you don't compute buckets yourself in application code, only when you want to reason about or test the rollout's behavior offline:

const result = client.evaluate("user-42", "new-checkout-flow", {
  attributes: { plan: "pro" },
  fallback: false,
});
console.log(result.value, result.reason);

Where to go next

A staged rollout treats every subject the same at each percentage. If you need to target a specific group first, for example enterprise customers before everyone else, that's a targeting rule on the flag config, covered in the targeting-with-segments tutorial, since a config with manual rules can't also carry a percentage rollout.