I built a content engine that runs many brands from one config
# I built a content engine that runs many brands from one config
The first time you run a second brand through a working content pipeline, there is a shortcut that almost everyone takes. Copy the whole pipeline into a new folder, rename a few things, call it brand two. It works the same day, which is exactly why it is a trap. You now maintain two copies of the same code. Every bug fix happens twice. Add a third brand the same way and every fix happens three times, and sooner or later you fix a problem in three places and forget the fourth, and your brands quietly drift apart until nobody remembers which copy has which behavior.
I took that shortcut, felt the pain, and rebuilt everything around a different idea. One shared engine. One small config file per brand. Brand differences living in data, with the engine code identical for everyone. This post walks through how that works in practice and where it bites.
One engine, many configs
The core idea fits in a sentence. A single body of code knows how to do the work: write a script, generate a voice, render a video, schedule it, publish it. That engine has no idea which brand it is serving. Everything that makes brands different, the name, the voice, the subject area, the schedule, the look, lives in a per-brand config file. The engine reads the config for whichever brand it is running and behaves accordingly.
The payoff shows up the first time you improve something. With copies, an improvement to script writing has to be applied separately in every copy, and drift is only a matter of time. With a shared engine, you improve it once and every brand receives the change immediately. You stop maintaining several slightly different pipelines and start maintaining one thing that serves all of them.
The template does the heavy lifting
The piece that makes the architecture practical is a template, a complete working set of defaults for every choice the engine has to make. How long a script runs. What the captions look like. How the render is configured. When things publish. One brand acts as the reference, the cleanest and fullest version, and the template captures how that brand does everything.
Every other brand starts from the template and overrides only what genuinely differs. A new brand's config can be a dozen lines: its name, its voice, its subject area, its schedule. Those dozen lines plus the template produce a fully working brand. Each brand is defined by its differences from the default rather than by a full restatement of every setting.
Inheritance is also what keeps the brands consistent. Any setting a brand did not override is guaranteed identical across all of them, by construction, because it comes from the same template. Consistency stops being something you check for and becomes something the structure provides.
What belongs in config
My test for whether something goes in config is simple. If I can describe a difference as a fact about the brand, it belongs in config. Script length, voice, publish schedule, topic area, output destination, look and feel. All facts about the brand, all data.
If a difference is about how the work gets performed, the actual steps the engine takes, that is a warning sign. The steps are supposed to be shared. When one brand seems to need different logic, there are two honest answers. Either the behavior is something every brand should be able to opt into through a config value, in which case I build it that way, or it is so specific to one brand that it belongs in a narrow piece outside the shared core. It never gets to be a hidden fork inside the engine that only one brand walks down. That fork is invisible today and a maintenance nightmare in six months.
The rule: the engine never names a brand
Here is the discipline the whole design depends on, and the place people go wrong. Brand-specific logic stays out of the engine. The moment the code contains a conditional that says if this is brand three, do something special, the rot has started. The engine now knows about a specific brand, and that knowledge multiplies. A special case here, an exception there, and within months the clean shared core is a tangle of brand exceptions pretending to be shared code.
The concrete version comes up constantly. Say one brand needs longer scripts than the default. The lazy fix is a conditional in the engine keyed on that brand's name. The right fix is to notice that script length is simply a setting, promote it to a config value the engine reads for every brand, and let the one brand set it while everyone else keeps the default. The engine never learns the brand's name.
The gap between those two fixes feels tiny in the moment, a config value versus a quick conditional, and the conditional is always faster today. I have given in to that temptation and regretted it every time. Nearly every special-case urge has a config-value answer hiding behind it, and the discipline is to keep looking until you find it.
The blast radius, and the baseline that contains it
The architecture has a sharp downside that deserves respect. A shared core means a shared blast radius. Introduce a bug into the engine and every brand breaks at once. The same property that delivers a good change everywhere instantly delivers a bad change everywhere instantly. Sloppiness in the engine gets multiplied by the number of brands riding on it, so the core has to be solid in a way a throwaway copy never needed to be.
My safeguard is a protected baseline. Before any engine change ships, I run it against the reference brand and confirm that brand still produces exactly what it produced before. An unchanged reference is strong evidence the shared behavior survived. It is a simple habit, and it changed my relationship with the core. A shared engine you are afraid to touch decays into a shared engine nobody improves. The baseline turns it back into something I can change with confidence.
When this is overkill
Honesty about the cost: a shared engine with a template and inheritance is more upfront work than copying a pipeline once. If you will only ever run one brand, skip all of this. The abstraction is pure overhead with nothing to spread it across. At two brands it is a judgment call. At three or more the shared engine wins decisively, because the cost of keeping copies in sync grows with every brand while the cost of one engine stays flat.
My advice is to build the simple copy first and invest in the shared engine only when you actually feel the pain of drifting copies. Premature abstraction is its own trap, and it costs real time to build machinery for a scaling problem you may never have.
The compounding I did not expect
The benefit that sold me long after the build was how improvements compound. The first brand cost the full price of building the engine. Every brand since has cost a small config file, and all of them ride every improvement I make to the core, forever, at no per-brand effort. Scaling by data means you stop paying the full price for each new thing and start paying only for what genuinely differs.
So the takeaway I would leave you with: resist scaling by copying. Copying is fast exactly once, and then it becomes a tax you pay on every change for as long as the copies live. Build one solid engine, capture the standard behavior in a template every variant inherits, push everything that differs into small per-variant configs, and hold the line that the engine never names a brand. Adding the next brand becomes a config file and an afternoon. The discipline is harder than the copy, and it is the only version that still stands once you run more than a couple of things.
More breakdowns like this are on the [home page](/).
Get new guides and videos first — join the Telegram channel.