ToggleFleet
Migration guide · verified against first-party Flipper docs July 2026

Move one flag at a time.
Keep a rollback path.

ToggleFleet is not a drop-in replacement for Flipper. The five established gate concepts map, but the gem, initialization, actor identity, management surface, and method signatures differ. This guide keeps Flipper authoritative until each replacement flag is verified.

Recommended unitOne non-critical flag
Initial authorityFlipper result
VerificationParallel evaluations
RollbackKeep Flipper configured
Migrating with an AI agent

Hand this guide to your coding agent.

Copy a ready-made prompt containing the gate map, the ToggleFleet Ruby API, the actor-identity rules, and the verification order from this page. Paste it into Claude Code, Cursor, Copilot, or any agent with repository access. It is written to keep Flipper authoritative and to refuse the migrations that do not map.

You are migrating a Ruby application from the Flipper gem to ToggleFleet, a hosted
feature-flag service. Work strictly in the order below and stop for confirmation
before the final step.

CRITICAL CONSTRAINTS - do not violate these:
1. ToggleFleet is NOT a drop-in replacement for Flipper. Do not attempt a
   find-and-replace of Flipper calls.
2. Flipper stays authoritative for every flag until its ToggleFleet replacement
   has been observed agreeing. Never remove the Flipper gem, its adapter, or its
   flag definitions during this migration.
3. If a flag's behaviour cannot be reproduced exactly, leave it on Flipper and
   say so. Do not approximate.

GATE MAPPING (the only supported translations):
  Flipper boolean              -> ToggleFleet boolean
  Flipper actors               -> ToggleFleet target actors
  Flipper groups               -> ToggleFleet target groups + register_group predicate
  Flipper percentage_of_actors -> ToggleFleet percentage of actors
  Flipper percentage_of_time   -> ToggleFleet percentage of time

EXPRESSIONS (Flipper marks these experimental). ToggleFleet has no expression
language, but most expressions translate into a group predicate, which is plain
Ruby evaluated inside the application:
  property(:age).gte(21)          -> register_group(:over_21) { |u| u.age >= 21 }
  all(over_21, any(paid, vip))    -> one predicate with the same boolean logic
  now.gte(time("2026-03-01"))     -> a scheduled flag (set "enable at" on the flag)
  feature_enabled(:other_flag)    -> NO equivalent; compose both checks in app code
Translate an expression ONLY when you can read the property off the actor in
application code and reproduce the logic exactly. Leave it on Flipper when it
references another feature, when the rule is changed by non-engineers without a
deploy (a predicate requires a deploy to change), or when you cannot determine
the property source with certainty. State which you did for each flag and why.

Two warnings that matter:
- The same percentage does NOT produce the same cohort. The two systems bucket
  actors independently, so a 20% rollout selects a different 20% of users.
- percentage_of_time is non-sticky in both systems: the same actor can get
  different answers on consecutive calls. Do not use it where stickiness matters.

STEP 1 - INVENTORY (read-only, change nothing)
Find every Flipper call site. For each flag produce a table:
flag key | gate types used | environments | actor identity | supported?
Mark a flag unsupported if it uses expressions or anything outside the map.
Show me the table and stop if anything is ambiguous.

STEP 2 - ACTOR IDENTITY (the most common cause of a broken migration)
Determine what Flipper uses as the actor identifier. Flipper::Identifier commonly
yields "ClassName;id" (e.g. "User;123"), NOT the bare id. ToggleFleet derives its
id from #togglefleet_id, then #id, then to_s. If those differ, actor gates will
silently target the wrong users. Either define togglefleet_id on the actor class
to return exactly the string Flipper used, or migrate the stored actor values.
State which you chose and why.

STEP 3 - INSTALL (additive only)
Add the gem alongside Flipper; remove nothing:
  gem "togglefleet"
config/initializers/togglefleet.rb:
  ToggleFleet.configure do |c|
    c.sdk_key = ENV.fetch("TOGGLEFLEET_SDK_KEY")
    c.default = false          # returned if config is unavailable
  end
  # Group membership is decided by YOUR code, not by ToggleFleet:
  ToggleFleet.register_group(:admins) { |user| user.admin? }
  ToggleFleet.start            # background refresh; fork-safe as of v0.2.0
Recreate each supported flag in the DEVELOPMENT environment first. Do not touch
production configuration.

STEP 4 - SHADOW COMPARISON (Flipper still decides)
Evaluate both, log disagreements, return the Flipper result:
  flipper_result = Flipper.enabled?(:checkout_v2, current_user)
  tf_result      = ToggleFleet.enabled?(:checkout_v2, actor: current_user)
  Rails.logger.warn("[flag-migration] checkout_v2 flipper=#{flipper_result} togglefleet=#{tf_result}") if flipper_result != tf_result
  flipper_result   # Flipper remains authoritative
Percentage gates WILL disagree per actor by design - compare aggregate rates, not
individual users. Boolean and actor gates must match exactly; any disagreement
there is a bug to investigate before proceeding.

STEP 5 - CUTOVER (stop and ask me first)
Only after a quiet verification window with no unexplained disagreements, and only
one flag at a time, switch the call site to ToggleFleet and leave the Flipper
definition in place as a rollback path. Do not batch this.

DELIVERABLES:
- the inventory table from step 1
- which flags migrated, which stayed on Flipper, and why
- the actor-identity decision and its justification
- every file changed
- anything you were unsure about - say so rather than guessing

Reference: https://togglefleet.com/migrate-from-flipper and https://togglefleet.com/docs
00 / Read this first

Familiar model, different SDK.

!
This is not a drop-in replacement.

Do not remove Flipper, swap a constant, or copy production percentages without a staged verification period.

ToggleFleet currently supports five gates: boolean, actor, group, percentage of actors, and percentage of time. Flipper's current first-party documentation lists those five plus experimental expressions. ToggleFleet has no expression language, but most expressions translate directly: property comparisons become group predicates in your application, and time windows become scheduled flags. Expressions whose rules change frequently without a deploy may be better left on Flipper.

There is no automatic Flipper adapter import. Recreate supported flags deliberately so gate state, actor identity, and environment selection are reviewed rather than guessed.

01 / Inventory

Classify before changing code.

  1. List active flags with Flipper.features and record each stable key.
  2. For each environment, record which gates are enabled and the current values.
  3. For expression-based flags, note which are property comparisons or time windows (both translate) and which depend on other features or change without a deploy (keep on Flipper).
  4. Identify the actor classes and the value returned by flipper_id.
  5. Choose one non-critical boolean flag for the first migration.
FlagEnvironmentGatesActor IDDecision
checkout_v2StagingBoolean off · actors 2User;idMigrate first
spring_saleProductionExpression / time windowNoneKeep on Flipper

Rows above are worksheet examples, not customer or production data.

02 / Gate map

Map only behavior that both systems implement.

Flipper behaviorToggleFleet controlMigration note
BooleanBooleanDirect on/off concept; verify the target environment.
ActorsTarget actorsCopy stable IDs only after reviewing actor identity.
GroupsTarget groups + register_groupRe-register predicates in application code.
Percentage of actorsPercentage of actorsThe same percentage does not promise the same cohort.
Percentage of timePercentage of timeBoth are non-sticky; verify expected sampling behavior.
Expressions (experimental in Flipper)Group predicates · scheduled flagsMost translate directly — see expressions below.
02b / Expressions

Most expressions become a predicate.

Flipper expressions evaluate properties of an actor — Flipper.property(:age).gte(21) — composed with all and any. Flipper's own documentation currently marks them experimental and subject to change. ToggleFleet has no JSON expression language, and deliberately so: the same logic is expressed as a predicate in your application, which is ordinary Ruby with no operator vocabulary to outgrow.

Flipper expressionToggleFleet equivalent
property(:age).gte(21)register_group(:over_21) { |u| u.age >= 21 }
all(over_21, any(paid, vip))One predicate containing the same boolean logic.
now.gte(time("2026-03-01"))A scheduled flag — set enable at on the flag.
feature_enabled(:other_flag)No direct equivalent; compose the two checks in application code.
config/initializers/togglefleet.rban expression, rewritten as a predicate
# Flipper: Flipper.all(Flipper.property(:age).gte(21), Flipper.any(paid, vip))
ToggleFleet.register_group(:club_eligible) do |user|
  user.age >= 21 && (user.paid? || user.vip?)
end
!
The real tradeoff.

An expression can be changed in a dashboard without deploying. A predicate is code, so changing the rule requires a deploy — you can still flip the flag itself, target actors, and adjust rollout percentages from ToggleFleet without one. Decide per flag: rules that change often may be better left on Flipper.

Predicates also keep actor attributes inside your process. Nothing about a user's age, plan, or status is sent to ToggleFleet — only the flag key and the resulting decision matter, and the decision is computed locally.

03 / Actor identity

Preserve the identifier before copying actor gates.

Flipper's actor documentation uses flipper_id. ToggleFleet checks togglefleet_id first, then id, then the actor's string value. If your Flipper ID contains a class prefix or custom UUID, relying on ToggleFleet's default id can change actor matches and rollout buckets.

app/models/user.rbpreserve a custom Flipper identifier
class User < ApplicationRecord
  def togglefleet_id
    flipper_id
  end
end

Verify this value for representative actors before copying an actor allowlist. Even with the same actor ID, percentage-of-actors parity is not promised because the two SDKs use different bucketing inputs and implementations.

04 / Install in parallel

Keep both gems during verification.

Gemfiletemporary dual-SDK phase
gem "flipper"
gem "togglefleet", "~> 0.1"
config/initializers/togglefleet.rbserver-side key
ToggleFleet.configure do |config|
  config.sdk_key = ENV.fetch("TOGGLEFLEET_SDK_KEY")
  config.default = false
end
ToggleFleet.start

Create the same flag key in ToggleFleet's Development or Staging environment. Start with all gates off, then reproduce only the supported state you inventoried.

05 / Dual-read

Compare in parallel; keep Flipper authoritative.

Run both local evaluations for a limited period. Return the Flipper result while recording mismatches. Do not log personal actor data; use aggregate mismatch counts or an internal stable identifier with an appropriate lawful basis.

temporary migration wrapperFlipper remains authoritative
def checkout_v2_enabled?(user)
  flipper_result = Flipper.enabled?(:checkout_v2, user)
  toggle_result = ToggleFleet.enabled?(:checkout_v2, actor: user)

  FeatureFlagMetrics.increment("checkout_v2.mismatch") if flipper_result != toggle_result
  flipper_result
end

Expected mismatches are not always defects. Percentage rollouts can produce different cohorts. For the first pass, use boolean, explicit actor, or explicit group gates where the desired result can be checked directly.

06 / Cut over and roll back

Change authority only after a quiet verification window.

  1. Confirm the ToggleFleet environment's connection timestamp is current.
  2. Exercise true and false paths in automated and staging tests.
  3. Review mismatch metrics; explain every remaining mismatch.
  4. Change the wrapper to return the ToggleFleet result.
  5. Keep the Flipper call and configuration available for one release window.
  6. If behavior regresses, return the Flipper result immediately; no flag-data rewrite is required.
  7. After the rollback window, remove the old call and the flag from Flipper.
Cut over one flag, not the whole fleet.

A per-flag sequence limits blast radius and makes rollback a code-path choice rather than a database migration.

First-party sources · checked July 26, 2026

Compatibility claims come from the products' own docs.

Flipper and Flipper Cloud are trademarks of their respective owners. ToggleFleet is independent and is not affiliated with, endorsed by, or sponsored by them.

Start with a flag you can turn off safely.

Prove the migration in Development first.

Create a free workspace, reproduce one supported flag, and keep Flipper authoritative until the results are understood.