-
-
Notifications
You must be signed in to change notification settings - Fork 512
How to add feature flags via Flipper
We use the Flipper gem to manage feature flags. Flipper lets us:
- Turn features on/off without deploying
- Gradually roll out changes
- Target specific users or organizations
This page covers:
- How to access the Flipper UI
- How to create a flag
- How to use a flag in code
- How to target a subset of users or orgs
The Flipper UI is mounted in our Rails routes. By default, it’s available at: /flipper
If that path doesn’t load, check config/routes.rb for the mount:
# config/routes.rb
require "flipper/ui"
Rails.application.routes.draw do
# ...
mount Flipper::UI.app(Flipper) => "/flipper"
endTypically, the Flipper UI should only be accessible to admins or in non-production environments.
A common pattern:
# config/routes.rb
require "flipper/ui"
flipper_app = Flipper::UI.app(Flipper) do |builder|
builder.use Rack::Auth::Basic do |username, password|
# Simple example; replace with real auth
username == ENV["FLIPPER_USER"] && password == ENV["FLIPPER_PASSWORD"]
end
end
Rails.application.routes.draw do
mount flipper_app => "/flipper"
endOr we mount it only in certain environments:
if Rails.env.development? || Rails.env.staging?
mount Flipper::UI.app(Flipper) => "/flipper"
endYou can create flags either through the UI or in code. The UI is preferred for most use cases.
- Go to
/flipper - On the main page, look for a field labeled “Add Feature” or similar.
- Enter a feature key (lowercase, snake_case is typical), e.g.:
new_dashboardbeta_checkoutorg_billing_v2
- Click Add / Create.
The feature will now appear in the list, and you can configure its “gates” (who gets the feature).
You don’t have to pre-create a feature in code—Flipper lazily creates it when referenced—but for clarity you might see code like:
Flipper[:new_dashboard] # creates the feature record if it doesn't existThis is enough to make Flipper aware of the feature; you still control who gets it via UI or gates.
Once a flag exists, you can check it anywhere in your Rails app.
Assumptions:
- User (or another model) responds to
flipper_id(often implemented asidor"User:#{id}"). - You have access to
current_user(or an equivalent).
class DashboardsController < ApplicationController
def show
if Flipper.enabled?(:new_dashboard, current_user)
render :new_dashboard
else
render :old_dashboard
end
end
end<% if Flipper.enabled?(:new_dashboard, current_user) %>
<%= render "dashboards/new" %>
<% else %>
<%= render "dashboards/old" %>
<% end %>If you don’t have a current_user, pass in whatever actor makes sense (a user, org, or something with a flipper_id):
def perform(user_id)
user = User.find(user_id)
return unless Flipper.enabled?(:new_onboarding_flow, user)
NewOnboardingService.call(user)
endIf you only want a simple global flag (on for everyone or no one), you can omit the actor:
if Flipper.enabled?(:maintenance_mode)
# ...
endFlipper has several “gates” to control who sees a feature:
- Boolean: on/off for everyone
- Actors: specific users/orgs
- Groups: logical groups like
:admins - Percentage of actors: random rollout to a percentage of users
- Percentage of time: random rollout based on request time (less common for us)
We primarily use:
- Specific users/orgs
- Groups (e.g.,
admins) - Percentage of actors (gradual rollout)
By default, Flipper looks for #flipper_id on any object you pass as the second argument.
Example implementation on User:
class User < ApplicationRecord
def flipper_id
"User;#{id}"
end
endFor organizations:
class Organization < ApplicationRecord
def flipper_id
"Organization;#{id}"
end
endImportant: Use a namespaced flipper_id ("User;#{id}", "Org;#{id}") to avoid collisions.
Via UI
- Go to
/flipper - Click the feature (e.g.
new_dashboard) - In the Actors section, add a user by their
flipper_id- e.g.
User;123
- e.g.
- Save.
Only those users will see the feature, assuming the “boolean” gate is off.
Via code (if needed)
user = User.find(123)
Flipper[:new_dashboard].enable(user) # enable for this specific userYou can see this change reflected in the UI as well.
If your app is org-based, you can pass the org as the actor:
org = Organization.find_by!(slug: "acme-corp")
if Flipper.enabled?(:org_billing_v2, org)
BillingV2::Processor.new(org).run
else
BillingV1::Processor.new(org).run
endIn the UI:
- Under Actors, add
Organization;IDfor the org’sflipper_id.
This lets you roll out a feature org-by-org.
Flipper supports logical groups defined in code:
Flipper.register(:admins) do |actor|
actor.respond_to?(:admin?) && actor.admin?
endNow you can enable a feature for that group:
Flipper[:new_dashboard].enable_group(:admins)Or via UI:
- Open the feature
- Under Groups, enable admins
Anywhere you check the flag with a user:
Flipper.enabled?(:new_dashboard, current_user)Admins will automatically get the feature.
You can define multiple groups, such as :internal, :beta_testers, etc., as long as you register them with a block.
To softly roll out to a percentage of users:
- Go to
/flipper - Click the feature
- Under Percentage of actors, set a percentage (e.g. 10%)
Now approximately 10% of actors (based on flipper_id hashing) will have the feature enabled.
Usage in code stays the same:
if Flipper.enabled?(:new_dashboard, current_user)
# user may or may not be in the rollout bucket
endYou can increase the percentage over time (e.g., 10% → 25% → 50% → 100%) as confidence grows.
Check flag with user:
Flipper.enabled?(:feature_key, current_user)Enable for everyone:
Flipper[:feature_key].enableDisable for everyone:
Flipper[:feature_key].disableEnable for specific user:
Flipper[:feature_key].enable(user)Enable for org:
Flipper[:feature_key].enable(org)Enable for group:
Flipper[:feature_key].enable_group(:admins)Set percentage rollout:
Flipper[:feature_key].enable_percentage_of_actors(25) # 25%