Feature flags in Rails: Flipper, Rollout and Unleash compared

A feature flag is an if whose answer lives outside the code. You merge the new checkout flow switched off, turn it on for your own account, then for staff, then for five per cent of customers, and switch it off again in seconds if the error rate climbs, without a deploy. Rails has no built-in flag system, so the choice is between a gem that keeps flags in your own database or Redis and a client for a separate flag server. Three options cover most Rails teams.
Release figures are from RubyGems.org, November 2026. The current Rails line is 8.1.
What a flag library has to answer
Every tool below answers the same question, is this feature on for this caller?, but they differ on four points worth settling before you pick one:
- Where the state lives. In your primary database, in Redis, or on a server you call over HTTP.
- Which rules exist. All of them offer on/off and per-user switches. Groups ("all admins"), percentages of users and percentages of requests are where they diverge.
- What a check costs. A flag tested ten times per request should not mean ten queries.
- Who flips the switch. A console command is fine for developers; support staff and product managers need a screen.
Flipper: the default choice for most Rails apps
Flipper (1.4.2, May 2026, MIT) was started by John Nunemaker and is maintained by the team behind Flipper Cloud, its paid hosted service. The open-source gem works on its own. For Rails, the usual install is the Active Record adapter, which pulls in the core gem:
bundle add flipper-active_record bin/rails g flipper:setup bin/rails db:migrate
The generator writes an initializer and a migration for two tables, flipper_features and flipper_gates. Redis, Sequel, Mongo and a cache-store adapter exist as separate gems. Checks and switches read like this:
Flipper.enabled?(:new_checkout, current_user)
Flipper.enable_actor :new_checkout, current_user
Flipper.register(:staff) { |actor, _context| actor.respond_to?(:staff?) && actor.staff? }
Flipper.enable_group :new_checkout, :staff
Flipper.enable_percentage_of_actors :new_checkout, 5
Flipper.enable_percentage_of_time :new_cache, 10
An actor is anything with a flipper_id; Active Record models get one automatically. You can pass several actors in one check, for example a user and their organisation, and the feature is on if it is on for any of them. Percentage of actors is sticky: a given user stays in as the percentage rises, and the feature name is mixed in so the same users are not always first in line. Percentage of time is the opposite, a fresh roll on every call, which suits a cache or a new code path but not anything a user can see.
Three details from the Flipper documentation matter in production. First, by default Flipper preloads every feature at the start of each request and memoizes the result, so any number of checks costs one adapter call; the docs suggest keeping that until you have more than about a hundred features or a thousand enabled gates, then preloading a list instead. Second, since 1.3.0 a feature accepts at most 100 individual actors unless you raise actor_limit: per-user switches are meant for a handful of beta testers, and groups or percentages for everyone else. Third, since 1.2.0 Flipper switches to an in-memory adapter in the Rails test environment and resets it before each test, so every flag starts off and each test turns on what it needs.
For a web screen, flipper-ui mounts as a Rack app in your routes. It has no authentication of its own, so wrap it in a route constraint or the same guard as your admin panel. Newer pieces, such as expression rules evaluated against properties of an actor, are marked experimental in the docs, and the separate flipper-cloud gem is deprecated because Cloud support now ships in flipper itself.
Choose it when you want flags stored next to your data, a UI in an afternoon, and a hosted service available later without changing your calls.
Rollout: the Redis classic, rebuilt in 2026
Rollout was written by James Golick around 2010 and became the standard Redis-backed flag gem of the Rails 3 era. After years of small releases it changed substantially this autumn: version 3 (3.3.0, October 2026, MIT) separates feature logic from storage. Redis persistence moved to rollout-redis-adapter (0.4.0), and an Active Record adapter now exists (rollout-active_record-adapter, 0.2.0), both released in October 2026.
$rollout = Rollout.new(adapter: Rollout::Adapters::Redis.new($redis))
$rollout.define_group(:caretakers) { |user| user.caretaker? }
$rollout.activate_group(:chat, :caretakers)
$rollout.activate_user(:chat, user)
$rollout.activate_percentage(:chat, 20)
$rollout.active?(:chat, user)
The percentage rule is documented down to the formula, a CRC32 of the user id compared with the threshold, which makes it deterministic and easy to reason about. An option, randomize_percentage, picks users independently for each feature instead. Group definitions are blocks that must be declared at every boot; only the feature state is stored. Version 3 also reads optional YAML files in config/rollouts/ that describe each feature, with a description and any keys you like, such as an owner or a planned release date. That is a cheap way to keep an inventory of flags in the repository.
Upgrading from 2.x is not a version bump. The upgrade guide lists the breaking changes: Rollout.new(redis) now requires an adapter: keyword, rollout.storage is gone, feature metadata is stored as JSON so symbols come back as strings, and a separate Redis for history is no longer supported. Existing Redis keys stay where they are. The README also warns that rollout-ui and other dashboards may not support version 3 yet, so check yours before upgrading production.
Choose it when you already run Rollout or want a small, Redis-first library with no UI assumptions.
Unleash: a flag server with a Ruby client
Unleash takes the other route. Flags live on a separate Unleash server, which you can host yourself or buy as a service, and the unleash gem (6.7.1, August 2026, Apache-2.0) is a client. It downloads the full flag configuration in a background thread, every 15 seconds by default, and evaluates checks locally, so a check does not make a network call:
context = Unleash::Context.new(user_id: current_user.id.to_s)
UNLEASH.is_enabled?("new_checkout", context)
Rules such as gradual rollouts, user lists and constraints are defined in the server's interface, not in Ruby. The client writes the last configuration it received to a backup file, and can be bootstrapped from a file or URL so the app starts with known flags even if the server is unreachable. The SDK README covers the setup details, including Puma in clustered mode, where the client must be created in each worker after the fork. The same server serves SDKs for other languages, which is the main reason to choose it: one place for flags across a Rails monolith, a mobile app and a few services. Commercial services such as LaunchDarkly follow the same model with their own Ruby SDKs.
Choose it when several applications in different languages need the same flags, or non-developers need a full rollout console with environments.
The clean-up nobody schedules
- Every flag is a debt. A flag that has been at 100 per cent for three months is dead code with a database lookup attached. Give each flag an owner and an expected removal date: Rollout's YAML files, a comment in the Flipper initializer, or a ticket all work. Then remove the check, the code path that lost, and the stored state, in that order.
- Test both sides. As long as a flag exists, both branches are production code. One test with the flag on and one with it off is the minimum; our guide to testing Rails applications covers the frameworks.
- A flag is not a state. If the answer depends on where a record is in its life (draft, paid, archived), that belongs in the model; see state machines in Rails. Flags are for code paths, not for business data.
- Guard the switches. Whoever can open the flag UI can change production behaviour for every customer. Treat that screen like an admin console, and prefer a tool that records who changed what (Flipper Cloud, Unleash, Rollout's optional history).
- Vet the gem like any other. Flags sit in your hottest code paths. Run the candidates through our gem evaluation checklist.
Summary
| Option | Where flags live | Latest release | Best fit |
|---|---|---|---|
| Flipper | Your database, Redis or others; optional hosted Cloud | 1.4.2, May 2026 | Most Rails apps; UI and test defaults included |
| Rollout | Redis or Active Record, through an adapter gem | 3.3.0, October 2026 | Existing Rollout users; small Redis-first setups |
| Unleash | Separate Unleash server, cached in the app | 6.7.1, August 2026 | Flags shared across several apps and languages |
Category guidesState machines
A status column plus rules about how it may change. Rails enums, AASM, state_machines and Statesman compared, from callbacks on the model to a full transition history.
Category guidesAdmin panels
How ActiveAdmin, Avo, Administrate, RailsAdmin and Madmin approach back-office interfaces for Rails, their dependencies and licences, and when to build your own admin.
Category guidesTesting
The Rails testing toolbox in 2026: Minitest or RSpec, fixtures or FactoryBot, system tests with Capybara, shoulda-matchers, and what replaced Webrat, Spork and factory_girl.