Features
Feature Flags
Ship a feature switched off, then turn it on from the dashboard — for everyone, a percentage, or one account. No redeploy.
Quickstart
Wrap the new code in flags.on(). The second argument is what you get when the flag is off, missing, or unreachable — it never throws.
1import { flags } from 'flowgrid-sdk';
2
3if (flags.on('new-checkout', false)) {
4 renderNewCheckout();
5} else {
6 renderCheckout();
7}Already set up?
This assumes you've called Flowgrid.init() — see the SDK Quickstart. Flags load with it; there's nothing else to set up.
Creating flags
Two ways, and they work together:
- In the dashboard — Feature flags → New flag.
- In your code — list it in
declare. It appears in the dashboard switched off, ready to turn on.
1Flowgrid.init({
2 webId: 'YOUR_WEB_ID',
3 flags: {
4 declare: [
5 'new-checkout',
6 { key: 'beta-banner', description: 'Beta banner on the home page' },
7 ],
8 },
9});
10
11// On a server
12const flags = await getServerFlags({ webId: 'YOUR_WEB_ID', declare: ['new-checkout'] });Declaring never turns a flag on and never changes a flag that already exists. Use the exact key you pass to flags.on() — lowercase letters, numbers, dots and dashes.
React & Next.js
1import { useFlag, Flag } from 'flowgrid-sdk/react';
2
3function Checkout() {
4 const enabled = useFlag('new-checkout');
5 return enabled ? <NewCheckout /> : <LegacyCheckout />;
6}
7
8// Or as a component
9<Flag name="new-checkout" fallback={<LegacyCheckout />}>
10 <NewCheckout />
11</Flag>Next.js: always pass visitorId
Without it, a signed-out visitor can get a different answer on the server than in the browser.
Want typos caught at compile time? List your keys once:
1import { defineFlags } from 'flowgrid-sdk';
2
3export const flags = defineFlags(['new-checkout', 'dark-mode'] as const);
4
5flags.on('new-checkout'); // ✅
6flags.on('new-chekcout'); // ❌ compile errorOther frameworks
Subscribe to a flag and get called whenever it changes.
1fg.flags.store('new-checkout').subscribe((enabled) => {
2 document.querySelector('#new-checkout')!.hidden = !enabled;
3});Who gets it
Add rules to a flag in the dashboard. They run top to bottom and the first match wins. If nothing matches, the flag's default applies.
| Rule | Matches on |
|---|---|
| Percentage | A random but fixed slice of visitors. Raising it only adds people. |
| Attribute | Any trait you pass to identifyUser() — plan, org, role. |
| User | Specific people, by id or email. |
| Environment | development, preview or production. Detected in the browser; pass it yourself on a server. |
| Region | The visitor's country. |
| SDK version | The Flowgrid SDK version the visitor is running. |
| Did an event | Whether the visitor has fired one of your events, ever or in the last N days. |
Traits you pass to identifyUser() become attributes you can target:
1fg.identifyUser('u_123', { plan: 'pro', org: 'acme', role: 'admin' });Rolling out to part of an audience
For “25% of pro customers”, use two flags: one with an Attribute rule (plan is pro), one with a 25% rule, and check both.
More than on/off
Give a flag named variants to run an experiment, or to change a value (copy, limits, settings) without a deploy. variant() returns the name, get() returns the value.
1const label = flags.get('checkout-copy', 'Buy now');
2
3if (flags.variant('checkout-copy') === 'urgent') { … }
4
5// Any JSON, up to 32KB
6const retry = flags.get('retry-policy', { attempts: 3, backoffMs: 250 });React: useFlagVariant and useFlagValue. Server: getServerFlags() has the same variant() and get().
Measuring a flag
Turn on Measure this flag and the flag gets an Impact panel comparing its variants on what happened after each visitor first saw it:
- Guardrails — errors from the feature, and page speed (LCP, INP, TTFB, CLS).
- Success — conversion, revenue per visitor, sessions, 7-day return and support tickets.
Pick the event that counts as a conversion in the panel. Measurement is off by default — switch it on only for flags you're testing. Uptime and server response times aren't measured.
Errors count only when they come from the feature itself, not from anything else on the page. <Flag> catches them for you. Elsewhere, use flags.run(). If the new code throws, visitors get the old path instead of a broken page.
1// Runs the visitor's path. If the new one throws, the error is
2// counted against the flag and the old path runs instead.
3const total = await flags.run('new-pricing', () => priceV2(cart), () => price(cart));
4
5// Anywhere else
6flags.reportError('new-pricing', error);
7
8// On a server
9fg.trackError(error, { flag: 'new-pricing', visitorId });Debugging
Ask why a flag gave the answer it did:
1flags.explain('new-checkout');
2// {
3// value: false,
4// reason: 'default', // kill_switch | rule | default | unknown_flag
5// source: 'persistence' // network | persistence | resolve | fallback
6// }Flags are cached in the browser for a day so they work instantly on return visits. Changes reach each visitor on their next page load, usually within a minute. To change caching, go to Feature flags → Delivery, or set it in code:
1Flowgrid.init({
2 webId: 'YOUR_WEB_ID',
3 flags: {
4 lookupPolicy: 'networkFirst', // or 'persistenceUntilNetworkSuccess' (default), 'networkOnly'
5 persistenceTtlMs: 60 * 60 * 1000,
6 },
7});Deleting a flag
The Feature flags page shows how often each flag is read. No reads in 30 days means the code is gone and the flag is safe to archive. Archived flags return the default in your code, and their history is kept.