Feature flags that live in your GitHub pull requests
A feature flag in GitHub is a file in your repo, and changing it is a pull request like any other change: a diff, a reviewer, a required check, a merge. There is no separate flag dashboard to log into, because the flag and the code it gates live in the same place.
Most flag tools do not work this way. The flag lives in a vendor’s web console, and the code lives in GitHub, so the two drift. Nobody remembers why new-checkout-v2 is still at 100% with a dead branch behind it, because the thing that would have answered that question, a pull request with a reviewer’s name on it, never existed. The flag just got toggled.
What changes is the review surface. A diff instead of a dashboard field. A CODEOWNERS entry instead of an admin toggle nobody configured. A branch protection rule instead of an audit log nobody reads until after the incident.
Key Takeaways
- A feature flag in GitHub is a file, so a flag change is a pull request with a diff, a reviewer, and a merge commit, same as any code change.
CODEOWNERScan route every flag file indif/experiments/active/to the team that owns that surface, so a flag never merges without the right eyes on it.- Branch protection rules can require a passing
dif validatecheck and at least one approval before a flag merges to main.- Review comments land on the YAML frontmatter itself (a weight, an audience filter, an owner), not a support ticket about a dashboard field.
- The tradeoff: a GitHub review and merge takes longer than a dashboard toggle, so this model fits most flags and not a sub-second kill switch.
What a feature flag pull request looks like
A flag change in GitHub is a normal diff. Here is a pull request raising a rollout from 10% to 25%, viewed the way GitHub renders it:
--- a/dif/experiments/active/new-checkout.md
+++ b/dif/experiments/active/new-checkout.md
@@ -6,8 +6,8 @@
variants:
- id: "off"
- weight: 90
+ weight: 75
- id: "on"
- weight: 10
+ weight: 25
metrics:
primary: completed_checkout
That is the whole change: the reviewer sees exactly what moved (10% to 25%), who proposed it, and can leave a comment on the line, the way they would on a line of application code. Nothing about this requires a special flag-review UI. GitHub’s pull request review flow already does the job.
Compare that to a dashboard toggle: a click, maybe an activity log entry if the vendor logs clicks at all, and no comment thread explaining why 25% and not 50%. The GitHub version has both, for free, because it is the same review flow as every other change in the repo.
CODEOWNERS gives every flag a reviewer
A flag with no assigned reviewer is how a flag change ships with nobody looking at it. GitHub’s CODEOWNERS file fixes that at the path level:
# .github/CODEOWNERS
dif/experiments/active/checkout-*.md @acme/checkout-team
dif/experiments/active/pricing-*.md @acme/growth-team
dif/surfaces/** @acme/growth-team
Open a pull request that touches checkout-cta-copy.md, and GitHub automatically requests a review from @acme/checkout-team. The flag’s owner field in the frontmatter says who to ask about it later; CODEOWNERS says who has to approve it now. Both point at the same team, because the flag and the surface it gates share an owner.
Branch protection on GitHub: a flag can’t merge without a check and a reviewer
A diff and a CODEOWNERS entry mean nothing if the merge button is still unguarded. GitHub’s branch protection rules close that gap on main:
- Require a pull request before merging. No one pushes a flag change straight to
main. - Require at least one approval. The
CODEOWNERSreviewer has to click approve, not just get notified. - Require the
dif validatestatus check to pass. A flag with weights that do not sum to 100, or a reference to a surface that does not exist, fails the check and cannot merge. The validating feature flags in CI post covers that check in depth. - Require branches to be up to date before merging. Two people cannot both ramp the same flag against a stale copy of the file.
Turn all four on, and the sequence for changing any flag is fixed: open a PR, get the review CODEOWNERS routes, pass the required check, merge, with no step anyone can skip.
Review comments land on the frontmatter
Because the flag is a file, review comments attach to the exact line, not a free-text box under a dashboard field. A real review thread on the diff above might look like this:
@sam-reviewer commented on line 10:
weight: 25
Why 25% and not the 15% we discussed in standup? Add a line to the
brief below so this isn't a mystery in six months.
@alex-author replied:
Guardrails held clean through the 10% ramp for a week, so we're
moving faster. Added the reasoning to the brief.
That exchange is permanent. It is part of the pull request, searchable in GitHub, and tied to the commit that actually changed the weight. A dashboard’s comment field, if it has one, is rarely reviewed before the change takes effect. Here, the comment and the approval happen in the same place, before the merge.
A PM checking why a ramp moved does not need a dashboard login to find out. They open the same pull request the engineer reviewed, read the thread, and see the reasoning next to the diff that caused it.
The tradeoff: review and merge is not a kill switch
A GitHub pull request is slower than a dashboard toggle. Opening a PR, waiting for CODEOWNERS to assign a reviewer, getting the approval, and merging takes minutes to hours, not milliseconds. If your build and deploy take ten minutes, your worst-case time to kill a flag through this flow is ten minutes plus however long the review takes.
For most flags, where the failure mode is “some users see the new thing a little longer,” that is a fine cost for getting a reviewed, commented, permanent record instead of an unreviewed click. For a payment-path kill switch that has to stop bleeding in under a second, a PR-based flow is the wrong tool for that one flag, and a streaming dashboard is the right one. Most flags are not that flag.
Getting started
Any flag stored as a file already gets GitHub’s review flow for free: a diff, a CODEOWNERS route, a required check, a merge commit. dif builds the flag format and the validator that CODEOWNERS and branch protection sit on top of. Install the CLI and scaffold a project:
npm install -g @dif.sh/cli
dif init
dif init writes a dif/ directory and the typed client, with no account or dashboard to set up. Add a CODEOWNERS entry for dif/experiments/active/, turn on branch protection with dif validate as a required check, and the next flag change already has a reviewer waiting.
See what feature flags are for the format itself, and feature flags in git for the broader workflow.
The feature flags page covers the product end to end.
FAQ
Can you review feature flags on GitHub like code? Yes. If the flag is a file in your repo, a flag change is a pull request with a diff, and GitHub’s normal review tools, line comments, required approvals, status checks, apply to it exactly like any other change.
How do you assign an owner to a feature flag on GitHub? Two ways, and they should agree. The flag file’s own owner field names who to ask about it later. A CODEOWNERS entry for that file’s path tells GitHub who must approve the pull request now.
Does every feature flag need its own pull request? It needs its own commit at minimum, so the diff is readable and the history is clean. A PR can bundle a flag with the code it gates, since reviewing both together is the point of keeping flags in the repo.
Can you require approval before a flag reaches 100%? Yes. Branch protection requires an approval on every pull request to main, including a ramp from 25% to 100%. There is no separate path that skips review just because the flag already exists.
How is this different from reviewing a flag in a LaunchDarkly dashboard? A dashboard toggle usually needs no review at all, or at best a vendor-specific approval feature bolted on. A GitHub pull request reuses the review flow your team already runs on every other change, with no second system to configure. The open-source LaunchDarkly alternative post compares the two models in full.
Does GitHub review replace flag validation? No, they do a different job. Review catches whether the change makes sense (is 25% the right number). dif validate, run as a required status check, catches whether the file itself is well formed (do the weights sum to 100). A pull request wants both before it merges.