Muhammad Azeez

Feature flags with one Postgres table

In April I had to record a screen video for Meta’s app review (a huge pain, to be honest). The feature I was showing needed a permission Meta hadn’t granted yet, so I couldn’t turn it on for everyone. Recording it locally wasn’t realistic either, because most of it depends on real webhooks. So I added a feature flag: the feature stayed off in production except on the one business I was recording.

That was the first flag. Since then, I use one whenever I want to move fast without customers paying for it:

A flag is checked everywhere the feature lives: the API, background jobs, the AI agent and the app.

This is the admin page for flags. Automations is on for everyone now, and the two business overrides underneath are left over from when it was on for only those two businesses:

Feature Flags Dashboard

To keep thing simple, there’s no flag service. Flags are rows in a key-value table in PostgreSQL:

1CREATE TABLE feature_flags (
2    key         TEXT PRIMARY KEY,   -- "automations" or "automations:<business id>"
3    value       TEXT NOT NULL,      -- "on", "off", "0.1", a model name...
4    description TEXT,
5    updated_at  TIMESTAMPTZ DEFAULT NOW()
6);

To decide whether a feature is on, I check from most specific to least:

  1. An override for this business or channel
  2. The global value
  3. The default in code, which is almost always “off”

An override can go either way. A feature can be on for everyone except one business, or off for everyone except one.

If reading the override fails, the answer is off. It doesn’t fall back to the global value. The override is often what keeps a business out of a rollout, and a database hiccup shouldn’t let it in.

Flags can have different values:

Feature flags have allowed me to decouple physical deployments from delivering changes to to production. It also allows me to merge PRs much faster and make them smaller.

#feature-flags #postgresql