Tenancy & Routing
How one deployment serves many companies without mixing their data
Each company is a Better Auth organization. Its slug is the first path segment, so
rentals.iairu.com/acme-rentals/catalog belongs to the company with the slug acme-rentals.
Company names
A valid name is 3 to 40 characters: lowercase letters, digits and hyphens, starting and ending with a letter or
digit. A list of reserved words (such as docs, login, signup, cms,
account, api, admin, pricing and billing) cannot be claimed, so a
company can never shadow a platform page. The same list is enforced when a company is created and by the proxy.
The proxy
src/proxy.ts runs before rendering. A first path segment that is neither a platform route nor a valid
company name gets a real 404 status immediately. Whether a well-formed name belongs to an existing
company is decided by the page.
Pages stream, so by the time the page finds that a well-formed company does not exist the status is already
200. The page then renders "not found" and is marked noindex, which keeps it out of search
results. A real 404 for these is a known limitation.
Data isolation
Every query on a company-owned table goes through inCompany(table, companyId, ...), which adds
company_id = ? to the filter. The company id comes from the signed-in staff member's membership, never
from the request. Public pages look the company up by slug and only read items and content that are public and published.
Isolation is enforced in the application. Postgres row-level security is not used yet; see the roadmap.
Staff gate
Everything under /[company]/cms calls requireStaff(company, [resource, action]):
- Unknown company: not found.
- Signed out: redirect to
/[company]/cms/login. - Signed in but not staff of this company (for example a renter): the "no access" page.
- Staff without the required permission: back to the dashboard with a notice.