I’ve spent the past week migrating Unleash feature flags from several projects into one, mostly to clean them up and keep them in one place. Here’s what I was dealing with.
- Parent flags can’t depend on a flag in another project
- Segments can’t be shared across projects
- Visibility drops when one feature is spread over several projects
- Cross-team coordination gets expensive for the same reason
Some of these have a simple fix. Removing the project scope from a segment (a reusable bundle of constraints that any strategy can reference) makes it available to every project. Others have none. A parent flag in one project simply cannot reference a child in another. I’m sharing some stats and what I learned along the way.
Stats
Thirteen legacy flags in the consultation project became eleven flags in ezyvet
(three batch-tracking flags collapsed into one). Ten of the eleven new flags
reference exactly the same segments as their legacy counterparts, so targeting
is edited in one place and the pair cannot drift.
| flag | inline strategies removed | unique sites | segments created |
|---|---|---|---|
| ai-summary | 12 | 4,769 | 8 |
| billing-triggers-sidebar | 34 | 724 | 4 |
| flat-note-taking | 34 | 4,971 | 4 |
| flat-procedures | 13 | 4,070 | 7 |
| flat-revisit | 1 | 1 | 1 |
| re-skin-note | 4 | 14 | 3 |
| new-section-headers | 4 | 402 | 3 |
| note-checklists | 1 | 1 | 1 |
| note-tables | 2 | 5 | 2 |
| shorthand-table | 1 | 1 | 1 |
| total | 106 | 14,958 | 34 |
Decisions Made
- Left the development environment out of the segment work since these flags can be toggled by hand locally.
- Reused existing segments wherever possible by making them global.
- Some flags simply had too many inline strategies. These were consolidated and folded into segments of up to 1,000 values each, which is the cap.
- Some lists made more sense as exclusions (on by default, off for a listed set
of sites), so those were flipped from an allowlist to
NOT_IN. - Created segments per flag, named
clinical.<feature>.<env>.<what>, rather than one shared segment per targeting rule. During a migration, predictable names and a bit of duplication felt safer than sharing. - Did the first three by hand over a couple of days, then scripted the whole
loop. The last seven took an afternoon. Tried a few orderings for the cutover
along the way and landed on this one.
- create the segments (and the target flag if it doesn’t exist yet)
- attach the segments to the target flag with its environments still off, and validate for parity against the legacy flag
- attach the same segments to the legacy flag next to its inline strategies, and validate again
- disable the legacy inline strategies, and validate again
- delete them, and validate one last time
- turn on the target flag’s environments to match the legacy flag
- Dual-read both flags from the application until the legacy one is removed.
Lessons
- Inline constraints don’t scale across feature flags. They’re fine for a one-off on a single flag. Anything shared by more than one flag belongs in a segment, so there is one place to read it and one place to change it.
- Project scope on a segment becomes a blocker. A scoped segment can’t be referenced from another project, and features rarely stay inside one. Prefer global segments, and accept that editing them needs root-level permissions.
- Allowlists turn into exclusion lists. Once most of the fleet is on the list, it’s cheaper to name who is off. Decide the flip early instead of letting the list grow. Two of mine went from 724 to 93 and from 4,971 to 1,123 sites.
- Parity is provable. At 100% rollout the stored constraints are the whole story, and the playground runs the real engine on demand. I stressed far more than I needed to. Once the approach is settled, trust the API and the playground and check every step mechanically.
- Script it. Because parity is provable, a machine can check every step, which makes a script both faster and safer than hands in the UI. Three flags by hand took days. Seven by script took an afternoon.
If I did this again I’d settle the per-flag versus shared segment question before creating a single segment, and I’d write the script before touching the first flag.