Trunk-Based Development
Ship incomplete features to main safely using feature flags
Trunk-based development is a branching strategy where all developers commit to a single branch (main or trunk). Feature flags make this practical by letting you merge incomplete work without exposing it to users.
The problem with long-lived branches
Long-lived feature branches cause:
- Merge conflicts — the longer a branch lives, the harder it is to merge
- Integration risk — bugs are discovered late when branches finally merge
- Slow feedback loops — code reviews are large and hard to review
- Deployment bottlenecks — releases are blocked by unfinished features
How flags solve this
With feature flags, you can:
- Merge to main daily — wrap incomplete work behind a flag
- Deploy continuously — the flag is off, so users don't see unfinished features
- Integrate early — catch conflicts and bugs as they happen
- Release independently — enable the flag when the feature is ready
Traditional: With flags:
main ────────────────────── main ─── deploy ─── deploy ─── deploy
\ / ↑ ↑ ↑ ↑
feature-branch (weeks) / commit commit commit commit
\_____________________/ (flagged) (flagged) (flagged) (flag ON)
Workflow
Step 1: Create the flag first
Before writing any code, create a boolean flag in the dashboard:
- Key:
new-billing-page - Type: Boolean
- State: Disabled in all environments
Step 2: Write code behind the flag
if (client.isEnabled('new-billing-page')) {
return renderNewBillingPage()
}
return renderCurrentBillingPage()
import { Feature } from '@flagpool/react'
function BillingPage() {
return (
<Feature flag="new-billing-page" fallback={<CurrentBillingPage />}>
<NewBillingPage />
</Feature>
)
}
Step 3: Merge to main daily
Commit and push your work daily. The feature is hidden behind the flag, so it's safe to deploy even if incomplete.
git add .
git commit -m "feat: new billing page - add payment method section (flagged)"
git push origin main
Step 4: Enable when ready
When the feature is complete and tested:
- Enable the flag in Development — test locally
- Enable in Staging — validate with QA
- Enable in Production — roll out (start with a low percentage if you want)
Step 5: Clean up
Once the feature is fully shipped:
- Remove the flag check from code
- Delete the old code path
- Archive the flag in the dashboard
Handling incomplete UI
When a feature spans multiple components, use the same flag key everywhere:
// Navigation
<Feature flag="new-billing-page">
<NavLink to="/billing-v2">Billing</NavLink>
</Feature>
// Routes
<Feature flag="new-billing-page" fallback={<OldBillingRoute />}>
<NewBillingRoute />
</Feature>
// API layer
if (client.isEnabled('new-billing-page')) {
return fetchFromNewBillingAPI()
}
return fetchFromLegacyAPI()
Using one flag for the entire feature ensures everything toggles together.
Best practices
Use prefixes for work-in-progress flags
wip_new-billing-page
wip_redesigned-settings
This makes it easy to identify flags that exist only for development purposes and should eventually be cleaned up.
Keep flag code minimal
The flag should control which code path runs, not contain complex branching logic:
// Good — clean separation
if (client.isEnabled('new-billing')) {
return <NewBilling />
}
return <OldBilling />
// Bad — tangled logic
return (
<div>
{client.isEnabled('new-billing') ? <NewHeader /> : <OldHeader />}
<SharedContent />
{client.isEnabled('new-billing') ? <NewFooter /> : <OldFooter />}
{client.isEnabled('new-billing') && <NewSidebar />}
</div>
)
Set a cleanup deadline
When creating a flag, agree on when it should be removed. Flagpool's stale flag detection (Starter plan and above) helps identify flags that have been on for too long.
Next steps
- Feature Flags — core concepts and lifecycle
- Environments — test flags across environments
- Creating Your First Flag — dashboard walkthrough