how to

Ship your open-source and SaaS versions from one repository — the exact workflow

the short answer

To ship open-source and SaaS versions from one repository, define your editions as named tiers (open-source, saas, pro), express every paid difference as a boolean feature flag with a default plus per-tier overrides, commit a generated tiers.json that resolves every flag per tier, and have CI build each edition from the same commit with its tier name as a build parameter — tier·dev manages the tiers, the flags, and the generated config.

The moment an open-source project grows a paid edition, the repository wants to split in two. A private fork appears 'temporarily', an enterprise branch starts collecting cherry-picks, and within a quarter every bugfix ships twice or — worse — ships once, to whichever edition the author remembered.

The fix is structural, not heroic: stop encoding the business question (who gets this feature?) in git topology (which branch is this commit on?). One repository, named tiers, flags as data. Here is the whole workflow, end to end.

0 branchestier·dev's model keeps zero long-lived edition branches — the entire open-source/saas difference lives in one generated config file
tierdev.ogbuilds.ai/repos/acme-analytics
tier·dev
dashboardconnect
+ new flag
acme/acme-analytics
github · 3 tiers · 4 flags · one codebase
open-sourcesaaspro
flagopen-sourcesaaspro
PRO_DASHBOARD_ENABLED
pro analytics dashboard
off
on
on
SSO_SAML_LOGIN
enterprise sso via saml
off
off
on
USAGE_BILLING
metered billing hooks
off
on
on
COMMUNITY_TELEMETRY
anonymous usage pings
on
off
off
tiers.json · "saas": { "PRO_DASHBOARD_ENABLED": true }copy

where this happens in the app

the flag matrix is the whole workflow in one screen — every feature flag, every tier, every resolved value, and the generated config line you commit to the repo.

  1. 1flags are screaming_snake_case facts about your product — created once, resolved per tier.
  2. 2each tier column shows the resolved value: defaults inherited, overrides set deliberately.
  3. 3the generated tiers.json is what your build reads — no sdk, no runtime call.

why branches fail as a packaging mechanism

Git branches answer 'what changed, when, by whom'. Editions ask a different question — 'who is this feature for?' — and a long-lived branch is a terrible place to store the answer: it drifts, it conflicts, and it hides the edition difference inside a diff only one engineer can read.

Flags invert that. The difference between your community edition and your cloud edition becomes a column of on/off values that anyone — engineering, support, a prospective customer doing due diligence — can read in one screen.

what stays out of the public repo

Most paid code can ship dark: present in the repository, disabled by flag in the open-source build. For the small remainder where visibility itself is the problem — licensed integrations, partner work under NDA — keep one thin private package that only the SaaS build imports, gated by the same flag. That's one private module, not a private fork of your whole product.

If a feature must be physically absent from the open-source artifact, point your bundler's compile-time defines at the resolved tier config and dead-code elimination strips the gated modules from that build entirely.

where tier·dev fits in the loop

tier·dev is the bookkeeping layer: connect the GitHub repo, define the tiers, manage flags in a matrix with per-tier overrides, and copy the generated tiers.json, tiers.yaml, or dependency-free flags.ts into the repo. There is no runtime SDK and no network call in your hot path — the config is yours, committed and reviewed like any other code.

how it works

  1. 01

    connect the repository

    Sign in to tier·dev and connect your GitHub repo via OAuth — it reads repo metadata only, never your source code.

  2. 02

    name your tiers

    Create the editions you actually ship: open-source, saas, pro. Tiers are labels on one codebase, not branches.

  3. 03

    flag the first difference

    Create a flag like PRO_DASHBOARD_ENABLED with default off, then override it on for the saas tier.

  4. 04

    commit the generated config

    Copy the generated tiers.json (or yaml, or flags.ts) into the repo and gate the feature with a one-line check.

  5. 05

    parameterize CI

    Build each edition from the same commit with PRODUCT_TIER set — every fix lands in every edition on the next build.

frequently asked

can't open-source users just flip the paid flags on?
Self-hosters who flip flags were never the customer — what a SaaS sells is hosting, updates, SSO that's supported, and someone to call. Open-core companies have shipped honestly-flagged code for years; if a feature truly must be absent, strip it at build time from the same config.
do I need to migrate off my enterprise branch first?
No — merge it back one feature at a time. Pick a divergent feature, wrap it in a flag on main, delete it from the branch. Most teams retire the branch within a few sprints without freezing releases.
does this add a runtime dependency to the open-source build?
None. The open-source distribution reads a committed config file at build or boot. There's no tier·dev SDK and no network call — clones of your repo work offline, forever.

Last updated June 12, 2026

ready to try tier·dev?

connect a repo