Skip to Content

Feature flags your build can decide

The build decides every flag it can before deploy, and only flags that depend on the visitor wait. Decouple release from deployment.

Your flags live in your repo as a file, changed in the same pull request as your code.

Get startednpmGitHub

npm install @evanion/feature

Decided before deploy

Set once for every visitor when the site builds.

Brass: Birmingham

Brass: Birmingham is a strategy game, so the rollout can reach its visitors.

Old gallery
New gallery

The new gallery waits for the visitor.

Decided when a visitor arrives

Set for each visitor when they arrive.

11 of 48 visitors see the new gallery.

Raising the share only adds visitors; nobody who has the new gallery loses it.

Each dot is a visitor, told apart by their id. Filled means they see the new gallery. The same visitor always gets the same answer.

Tap or hover a dot to see that visitor.

11 of 48 visitors see the new gallery.

Your flags

createFeatures([
  { key: 'new-gallery', enabled: true,
    rules: [{
      when: [{ field: 'category', op: 'eq', value: 'strategy' }],
      rollout: { percent: 25, by: 'targetingKey' },
    }] },
])

// before deploy: one call per page
features.plan({ game, category: 'strategy' })
// when a visitor arrives: one call per request
features.resolve({ game, category, targetingKey: visitor.id })

Stop the domino effect

Your customers never see a feature whose foundation is gone.

New features often rely on older ones. Turning off a foundation feature can leave dependent features in a broken state.

You can make features depend on one another. Before you commit, the toggle lists exactly what will go off. After the change, each feature says why it is off.

  • New checkouton
    • Express pickupon
      • Demo-night bookingon
createFeatures([
  { key: 'new-checkout', enabled: true },
  { key: 'express-pickup', enabled: true, dependsOn: ['new-checkout'] },
  { key: 'demo-night-booking', enabled: true, dependsOn: ['express-pickup'] },
])
A preview shows that disabling the checkout foundation also disables express pickup.
See how dependencies work →

Tests that don't jump

Run clean A/B tests where visitors stay in their assigned group.

Shuffling people between versions mid-test ruins your data and confuses your users.

Visitors who keep their ID keep their variant when you move a two-way split. Only those between the old and new boundary change. You can also pin a specific group to one variant.

50% blue

25 of 48 see the blue button · Move the split to see who changes

Each dot is a visitor. Filled ones see the blue button, hollow ones the original. A dashed outline is a bookseller pinned to blue. A ring is a visitor who switched just now.

Tap or hover a dot to see that visitor.

{ key: 'cta-color', enabled: true,
  variants: [{ name: 'control', weight: 50 }, { name: 'blue', weight: 50 }],
  rules: [],
}
A visitor always sees the same version unless you move the split. The line under the slider counts who sees blue and who changed.
See how splitting works →

Launch while you sleep

Set your release date and forget it.

You want to coordinate a big launch without being awake to flip a switch or trigger a deployment.

A rule turns a feature on after the time you choose. Because the decision depends only on the clock, the server and browser always stay in sync.

See how it works →

No flicker on load

Eliminate the jarring visual jump on page load.

Pages that decide flags only in the browser often show a default version for a split second before swapping to the correct one.

The server decides the flags and sends those decisions inside the page. The browser uses them before the first paint and can check the server's decisions against its own copy to report differences.

See how it works →

Built for React

Wrap your app once and any component can ask if a feature is on.

npm install @evanion/feature @evanion/react-feature
import { createFeatures } from '@evanion/feature';
import { FeatureProvider, useFeatureEnabled } from '@evanion/react-feature';

const features = createFeatures([{ key: 'new-checkout', enabled: true }]);

function Checkout() {
  const isNew = useFeatureEnabled('new-checkout');
  return isNew ? <p>New checkout</p> : <p>Old checkout</p>;
}

function App() {
  return (
    <FeatureProvider features={features}>
      <Checkout />
    </FeatureProvider>
  );
}
Read the guide →

Questions

What is @evanion/feature?
It is an open source TypeScript library (MIT). You define your rules, rollouts, variants, and dependencies in a file and call it from your API, build, or browser.
Does it need a server of its own?
No. There is no service to call. The file ships with your app, or you can serve it as a plain file to reload it in a running app.
How do anonymous visitors get a stable variant?
Bring your own visitor ID. The library never sets a cookie and never stores which variant anyone got. Signed-in visitors use the account ID. Anonymous visitors use an ID the site already sets, such as a session, cart or analytics ID. If the site has none, you can set a first-party ID cookie yourself, under your own consent rules. Without any ID, the visitor gets the default variant, and percentage rollouts leave them out. To keep a variant across sign-in, use variantBy or stickyVariants.
Does it work with static sites?
Yes. A build decides every flag it has enough information for and writes the answer into the page. Flags that depend on the visitor are decided when the visitor arrives.
Is there React support?
Yes, via @evanion/react-feature. It provides a provider and hooks to access flags anywhere. Server decisions reach the browser so there is no flicker.

Feature flags your build can decide

The build decides what it can before deploy, and only flags that depend on the visitor wait. Decouple release from deployment. Install the library and follow the getting started guide to begin.

Get startedAPI referenceGitHub