Move ENV Var Feature Flags to Flipper
When ENV var feature flags are enough, when they stop being enough, and how to move them to Flipper one flag at a time without changing behavior in production.
An ENV var is the simplest feature flag there is. if ENV["NEW_CHECKOUT"] == "true" needs no gem, no table, and no UI, and for some flags that's all you'll ever need. This guide covers when to keep them and how to move the rest to Flipper, one flag at a time.
Keep ENV vars when
- The flag is on or off for everyone, and it rarely changes.
- Waiting for a restart or deploy to change it is fine.
- It's really configuration, like an API host or a timeout, and not a product decision.
- You have a few of them, and the people who change them already have access to your host's config.
Move to Flipper when
| You want to | ENV var | Flipper |
|---|---|---|
| Turn a feature on | Change config, usually a restart | One call or a click, no restart |
| Turn it on for one customer | Build it yourself | Enable an actor |
| Roll out to 10% of users | Build it yourself | Percentage of actors |
| Turn it on for staff | Build it yourself | Groups |
| Let non-developers change it | Give them host access | Flipper UI or Cloud |
| See who changed what | Your host's logs, if it keeps them | Audit history in Cloud |
| Find flags nobody checks anymore | Search the code | Telemetry in Cloud |
The first time a teammate asks "can we turn this on for just one customer?" is usually the time to switch.
Move one flag at a time
You don't need to move everything at once. Each flag below keeps its current value in production the whole way through, so nobody sees a change until you make one.
1. List your flags
Search for every ENV var your app reads, then pick out the ones that act as on/off switches. Leave real configuration where it is.
$ grep -rnE "ENV(\[|\.fetch)" app config lib
2. Install Flipper
If you haven't already, add the gem and an adapter. For a Rails app with Active Record:
$ bundle add flipper-active_record
$ bin/rails generate flipper:setup
$ bin/rails db:migrate
Deploy this on its own. Nothing checks Flipper yet, so nothing changes.
3. Copy each value into Flipper
Run this once in each environment, before you deploy any code that reads the flag. A flag that doesn't exist yet is off, so if you skip this step a feature that's on today turns off when the new code ships.
Flipper.add(:new_checkout)
Flipper.enable(:new_checkout) if ENV["NEW_CHECKOUT"] == "true"
Use whatever comparison your code uses today. If it treats any non-empty value as on, match that here.
4. Change the check
# Before
if ENV["NEW_CHECKOUT"] == "true"
# After
if Flipper.enabled?(:new_checkout, current_user)
Passing an actor changes nothing while the flag is fully on or off, and it's what lets you target one customer later. If a check runs while your app boots, such as in an initializer, move it to where the feature runs, because a value read at boot won't see later changes until the next restart.
Update your tests too. See testing for how to turn flags on and off in them.
5. Delete the ENV var
Once the new check is deployed everywhere, nothing reads the old value. Remove it from your host's config, from .env files, and from your setup docs, so nobody changes it and wonders why nothing happened.
Catch typos
An ENV var with a typo reads as unset, and so does a Flipper feature with a typo. The strict adapter warns or raises when your code checks a feature that doesn't exist, and it's set to warn in development and test by default.
Next
- Naming features so your new flags stay easy to find.
- Upgrade to Cloud when you want audit history, telemetry, and environments in one place.
Get audit history, rollbacks, advanced permissions, analytics, and all of your projects in one place.
You can choose from several tiers to sponsor Flipper on GitHub and get some great benefits!