One codebase, several products

How we turned a flashcard app into a platform that launches focused learning sites, without forking the code or maintaining two apps.

Photo by Luke Chesser on Unsplash

Braineos is our flashcard platform. When we wanted to launch Finance Brain, a free financial literacy course site, the obvious move was to copy the code and start changing it. We didn’t, because a fork is two apps to maintain forever: every bug fixed twice, every dependency upgrade done twice, and the two drifting further apart each month.

Instead, one codebase now deploys as several products. Here’s how that works, and what we’d tell anyone considering the same thing.

Pick the product at build time

Each deployment sets one environment variable, a site ID. At build time that ID selects a site profile: the product’s name, logo, colours, copy, legal text and which features it has.

The rule that makes this work is strict: no component hard-codes anything product-specific. The navigation doesn’t say “Braineos”; it says whatever the profile’s name is. Reviewing for this is cheap if it’s a rule from day one, and painful to retrofit, because product names hide in page titles, emails, error messages and alt text.

Secrets and deployment details stay out of the profile and in each deployment’s environment. The profile is checked in and describes the product. The environment describes where it runs.

Feature flags enforced on the server

A focused course site doesn’t want everything the flagship app has. Finance Brain turns off user-created decks, challenges and some AI tools.

Hiding a button isn’t enough to turn a feature off. The API is still there, and anyone can call it directly. So every flag is enforced where requests come in: if a feature is off for this site, its endpoints answer “not found”, exactly as if they didn’t exist. Then the interface hides the buttons as a courtesy, not as the protection.

There’s one useful exception: site admins can still use some switched-off features. That’s how Finance Brain’s own course content gets authored with the same deck tools that regular users don’t see.

Separate data, shared code

Each product gets its own database and its own sign-in project. Users of one never appear in the other, and a problem in one can’t leak into the other. Both run the same migration history, so the schemas never drift.

This costs a little more to run than one shared database with a “site” column on every table. We think it’s worth it. Separation by construction is easier to trust than separation by every query remembering a filter.

Content as an import, not a rewrite

Finance Brain’s courses came from an existing library of lessons. Rather than re-enter them, we wrote an importer that turns that structured content into courses, lessons and multiple-choice decks, images included. When the source is corrected, we import again.

What it bought us

When Finance Brain needed an Android app, the mobile codebase got the same treatment: one app, built per site, with its own name, icon and store listing. Every improvement to the platform now ships to both products at once.

The trade-off is discipline. Every new feature needs a moment’s thought about whether all sites want it. In our experience that question is worth asking anyway.

Planning an app?

Start a project