Model your business
Where your sites, crews and customers actually go. Read this before writing any code.
Brightside Cleaning has two crews in Portland, one in Salem, and about 300 regular customers. Sarah owns it. She's building an app so customers book online instead of ringing the office.
Here's where all of that lives.
The five words
- Account — Brightside Cleaning, the company. Sarah gets one when she signs up, and never needs another.
- Application — the app she's building. She calls it Brightside Booking.
- Environment — an isolated copy of the estate. Sarah makes
devto build against andprodfor real customers. - Franchise — a group of places. Sarah makes one called Oregon.
- Location — somewhere she actually operates. Portland is one. Salem is another.
Brightside Cleaning the company
└── Brightside Booking the app being built
├── dev a place to break things
└── prod the real estate
└── Oregon a group of places
├── Portland a place
└── Salem a placeCustomers, bookings and invoices belong to a Location. When Sarah's app asks "who's booked for Thursday?", it's always asking about Portland or Salem — never both at once, unless it asks twice.
That's the whole model. The rest of this page is why each level exists, and what happens when your business doesn't look like Sarah's. (Environments get their own page — the one-line version is that each is a fully separate copy of everything below it, so test data can never touch prod.)
Why not just "locations"?
Fair question. If you only clean in one city, three of the four levels feel like paperwork.
They start paying off the first time you want to give someone partial access.
Sarah hires a Portland manager, Dev. Dev should see Portland's customers and bookings, and shouldn't see Salem's. That's a five-second job because Portland is a thing that exists on its own — she gives Dev access to Portland and nothing else.
Then Brightside buys a small company in Eugene. Sarah wants her regional manager to cover all of Oregon without re-granting access every time a site opens. So she grants Oregon, the Franchise — and Eugene is included automatically, along with anything she opens next year.
One place? One Franchise, then forget it.
Make a Franchise named after your company, put your single Location in it, and never think about it again. It costs one extra request during setup. It is not a concept you have to hold in your head.
Franchise is a bad name
We know. It says "franchised brand", but it means "a group of places".
Read it as a folder. Sarah's Oregon folder holds Portland and Salem. A national cleaning chain might have a West folder holding Oregon and Washington folders, each holding cities. A single-van operator has one folder with one thing in it.
Folders can go inside folders, as deep as you like. There's no separate "sub-franchise" thing — a folder inside a folder is just a Franchise with a parent.
Where your data actually sits
Each Location gets its own database. Portland's customers are physically separate from Salem's.
This matters more than it sounds:
- Nothing leaks. No query can accidentally return Salem's customers to a Portland screen, because they aren't in the same place.
- A site can be sold or handed over. If Sarah sells Salem, its customers, history and invoices go with it. You'd move it to the new owner rather than untangling rows.
- Nothing is shared by accident either. Sarah's standard price list doesn't appear in Salem just because she made it in Portland. Things you want shared — services, products, tax rates — are set up once for the whole app and pushed down to the sites that opt in.
- Creating a site takes a moment. A database has to be made, so a new Location is briefly "getting ready" before you can use it.
When your business isn't shaped like Sarah's
Which level am I talking to?
Every request lands at one level, and that decides what you send.
| What you're doing | Level | Send a Location? |
|---|---|---|
| Setting up environments, sites, staff, keys | Account and above | No |
| Anything with customers, bookings, invoices | Location | Yes |
For anything at the Location level, the request has to say which one — the
X-Location-Id header. Forget it and you get a 400 telling you so.