Skip to content
← Back to blog
·2 min read·

Put the Tenant Column in the First Migration

"Maybe more brands later" is the most expensive sentence in a spec. Adding the tenant key on day one costs a column. Adding it on day four hundred costs a lot more.

Put the Tenant Column in the First Migration

A client asked me for a marketing content engine for one brand, with a note that there might be more brands later. Maybe more later is the most expensive sentence you can find in a spec, because it usually gets ignored until later actually shows up.

Retrofitting a tenant is miserable

If the schema starts out single brand, every table assumes there's only one of everything. Adding a second brand afterwards means a new column on every table, a backfill, making it non-null, rewriting every unique constraint that should have been per brand, and then hunting down every query that forgot to filter. That last step is where the bugs live. A missing filter doesn't crash anything. It just shows one brand's content to another brand.

Up front it costs almost nothing

So every table got a brand foreign key from the very first migration, with exactly one brand in the database. Anything that has to be unique should be unique per brand, brand plus slug rather than slug on its own. In EF Core you can go a step further with a global query filter on the current brand, so a query that forgets still only sees one brand's rows.

On day one it's one extra column and a slightly longer key. Nobody using the system notices.

Keep the one-brand assumption in one place

There's still only one brand, and it would be silly to make the UI pretend otherwise. So the assumption lives in exactly two places in the application, the seed that creates the brand and the default selector that picks it. Adding a second brand means a new row and a picker. It doesn't mean touching every table in a live database.

The engine also runs a separate safety pass over everything the model generates, to keep it on brand and within policy. Keeping that per brand is a lot easier when everything it could check against already carries a brand.

When I don't bother

If there's no realistic chance of a second tenant, I don't add one. It's a small cost but it isn't zero, and building for things you won't need is its own kind of mess. The signal I've learned to listen for is the word later, when the client says it about brands, customers, regions or companies. That's the one worth designing for.

The project shipped over five milestones and runs in production for its first brand, with the second one an additive change whenever it comes. This is the kind of .NET backend work I do.

#.NET#EF Core#Postgres#multi-tenant#database design