# Rolling out a feature with a flag (/docs/guides/feature-flag-rollout)

This is how we release features on our own sites, and the flow we recommend for any feature flag. It has three steps:

1. **Release at 0%.** The code is live, nobody sees the feature, and a few people check it with a query parameter.
2. **Roll out in steps.** Raise the share from 0% to 100%, watching your events and errors after each step.
3. **Remove the flag.** Once the feature is at 100%, take the flag out of your code and archive it in Improve.

Every step can be undone without a deploy, until the last one.

## Set up the flag [#set-up-the-flag]

Create a flag with two options. The **first option is the one people get when nothing else applies**: a visitor outside the flag's audience, or a visitor the SDK can't identify. Make that the current behaviour.

| Option | Slug | Split |
| ------ | ---- | ----- |
| Off    | off  | 100   |
| On     | on   | 0     |

Set the flag to **Active**. A draft flag isn't in the datafile, so your code reads it as missing.

In your code, show the feature only for `on`, and treat anything else (`off`, a missing flag, `null`) as off. A misspelled slug or an unreachable datafile then hides the feature instead of showing it.

```tsx
const showNewCart = improve.getFlagValue('new-cart') === 'on'
```

## 1. Release at 0% and check it with a query parameter [#1-release-at-0-and-check-it-with-a-query-parameter]

At 0% the code ships without anyone seeing the feature. To check it in production, read a query parameter named after the flag before you read the flag: `?new-cart=on`.

With the client SDK, the simplest way is to write the parameter into the flag's cookie. The SDK keeps a visitor's option in a cookie named after the flag, and it trusts any cookie that holds one of the flag's options. The person keeps seeing the feature while they click through the site.

```ts
const FLAG = 'new-cart'

// Before the first getFlagValue: a valid option in the URL becomes this
// visitor's option.
const forced = new URLSearchParams(window.location.search).get(FLAG)
const options = improve.config?.flags[FLAG]?.options ?? []
if (forced && options.some((option) => option.slug === forced)) {
	document.cookie = `${FLAG}=${forced}; path=/; max-age=${60 * 60 * 24 * 30}; samesite=lax; secure`
}

const showNewCart = improve.getFlagValue(FLAG) === 'on'
```

`?new-cart=off` puts the same person back. On the server, check the parameter before calling `getFlagValue` and use its value instead.

A few things to keep in mind:

- **Only accept the flag's own options.** Ignore any other value instead of passing it on.
- **It is not access control.** Anyone who has the link sees the feature. Don't put anything behind a flag that must stay secret.
- **Keep previews out of search engines.** When a whole page only exists behind the flag, add `<meta name="robots" content="noindex">` when it is shown through the parameter.
- **Previews count.** A forced visitor's exposure and events are recorded like anyone else's, so a handful of people checking the feature show up in its numbers.

## 2. Roll out in steps [#2-roll-out-in-steps]

Raise the split of `on` in steps, for example 5%, 25%, 50% and 100%. After each step, look at the events and errors you care about before taking the next one. A change reaches your site within about a minute and a half (the datafile's [edge cache](/docs/api/datafile)), without a deploy.

If something is wrong, set the flag back to **Draft**. It leaves the datafile, your code reads it as missing, and everyone gets the current behaviour again, including the people who already had `on`.

<Callout type="warn">
	**A visitor keeps the option they were given.** The SDK stores it in the
	flag's cookie and trusts it on every later visit, so raising the share only
	changes what new visitors get. Someone who got `off` at 5% still gets `off` at
	50%, and setting `on` back to 0% doesn't take the feature away from people
	who already got it. To change it for everyone at once, remove the option from
	the flag (a cookie that holds an option the flag no longer has is ignored) or
	set the flag to Draft.
</Callout>

At 100%, remove the `off` option, so returning visitors who got `off` earlier switch over as well.

## 3. Remove the flag [#3-remove-the-flag]

A flag at 100% is finished work. Take it out of your code so the next person doesn't have to wonder whether `off` still matters:

1. **Find every place that reads the flag.** Mark them when you add the flag, so this is one search. We put a comment with the flag's slug next to each one, like `// FLAG(new-cart): …`, and say in it what the code becomes without the flag.
2. **Keep the `on` path and delete the rest**: the check, the `off` path and the query parameter.
3. **Deploy**, then set the flag to **Archived** in Improve.

Archive the flag only after the code that reads it is gone. Code that still reads an archived flag gets `null`, which is the `off` path again.
