ESC
> launch.rentals beta
Home › Docs ›Architecture › Tenancy & Routing

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.

Unknown company names

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]):

  1. Unknown company: not found.
  2. Signed out: redirect to /[company]/cms/login.
  3. Signed in but not staff of this company (for example a renter): the "no access" page.
  4. Staff without the required permission: back to the dashboard with a notice.