Skip to content
v0.3

Tenants & stores

Forge has two levels of grouping, and they answer different questions.

A tenant is who owns the data. A store is a front the data is sold through.

Each tenant’s data lives in its own Postgres schema. Not a tenant_id column that every query must remember to filter on — a schema. The kernel sets the search path for the transaction, and a query that forgets a filter simply cannot see another tenant’s rows, because they are not on the path.

There is also a system schema, forge_control, which holds what belongs to the platform rather than to any tenant. Commands marked System: yes run there.

Two things follow, and both matter to you:

  • A tenant is a unit you can move. Its data is a schema; a backup is a schema dump. There is no disentangling.
  • The isolation does not depend on application code being careful. It depends on where the tables are.

A tenant can run several stores: different domains, currencies, catalogues, price lists, shipping rules, payment methods. A product can be published to one store and not another; a price can differ per store.

A store is resolved from the request — a public read carries store, and a storefront resolves it from its own hostname. It is not a credential and it is not authorisation: knowing a store id lets you read the public face of that store, which is by definition what anyone with a browser can do.

  • Public reads take store. They have no credential to resolve a tenant from.
  • Internal reads and commands resolve the tenant from the credential. There is no tenant parameter to get wrong, and passing one would be a way to ask for someone else’s data.
  • Where a command needs to act on a specific store, it takes a store id in its input, and the kernel checks that the store belongs to the caller’s tenant.

It is not multi-tenancy as a discount. Every tenant gets the whole kernel; there are no features gated by plan, because there are no plans. The instance you run is the instance you run.

Where a customer wants a genuinely separate box — their own database, their own upgrade cadence, their own infrastructure — that is not a bigger tenant. That is their own instance, and the model supports it directly.