Feature Flags

Feature flags are defined in config/features.yml (OSS) and enterprise/config/premium_features.yml (EE). Each flag occupies a fixed bit position in the accounts.feature_flags column — the order in features.yml must never change.

Flag lifecycle

State Key Meaning
Active (none) Flag is in normal use
Deprecated deprecated: true Feature is done; hidden from all UI. Bit slot is still occupied.

When deprecating a flag, mark it with deprecated: true only. Do not delete or reorder the entry — the bit position must remain stable.

Claiming a deprecated slot for a new flag

When you need a new feature flag, reuse a deprecated slot instead of appending to the end of the list.

<aside> ⚠️

Never re-order the flags, all changes are to be made in-place

</aside>

Step 1 — Pick a deprecated slot

Find a flag marked deprecated: true in config/features.yml. Note its name (e.g. response_bot).

Step 2 — Rename the entry

Replace the deprecated entry in config/features.yml with the new flag. Keep the entry at the same position — only change the keys:

# before
- name: response_bot
  display_name: Response Bot
  enabled: false
  deprecated: true

# after
- name: your_new_flag
  display_name: Your New Flag
  enabled: false

Step 3 — Write a migration

Create a migration to disable the new flag on the relevant accounts using the Ruby feature flag API, the migration only picks the accounts that have the flag enabled. We save using validate: false this is so that unrelated errors don’t break the migration.

# db/migrate/YYYYMMDDHHMMSS_enable_your_new_flag.rb
class EnableYourNewFlag < ActiveRecord::Migration[7.1]
  def up
    Account.feature_<your_new_flag>.find_each(batch_size: 100) do |account|
      account.disable_features(:your_new_flag)
      account.save!(validate: false)
    end
  end
end

The bit position is preserved — existing feature_flags data on accounts is unaffected by the rename itself. The migration only changes the value (enabled/disabled) for the new flag.

Never append a new entry to the end of features.yml if a deprecated slot is available. Reuse a deprecated slot to keep the total flag count within the bigint limit (63 usable bits).

Step 4 — Run the disable script on the production instance

<aside> 🚨

DON’T SKIP THIS STEP

</aside>