Schema per tenant
One Postgres schema per tenant, selected per transaction via search_path - with optional database-enforced isolation.
Schema-per-tenant gives each tenant its own Postgres schema inside a shared database. Tables aren't
shared; tenant_acme.orders and tenant_globex.orders are different tables. You get stronger
separation than row-level without the operational cost of a database per tenant.
Supported on Knex, Lucid, Prisma, TypeORM, Sequelize, and Drizzle for PostgreSQL.
PostgreSQL only. Schema-per-tenant relies on a transaction-local search_path - a Postgres
namespace with no direct MySQL/MongoDB equivalent. On those databases, use
row-level or database-per-tenant.
How it's enforced
Knex, Lucid, TypeORM, Sequelize, and Drizzle set the connection's search_path to that tenant's schema -
transaction-locally, so it reverts before pooled reuse:
select set_config('search_path', 'tenant_acme', true);Every unqualified query in that scope resolves to the tenant's schema. The schema name is validated as a safe SQL identifier before it's ever used, and cross-placement access is rejected.
Prisma is different: runtime search_path does not route generated model queries. Its adapter leases a
Prisma 7 client created with the PostgreSQL driver's explicit { schema } option. Both mechanisms use
the same one-tenant-to-one-placement collision rule.
Adapter-enforced vs database-enforced
By default, isolation is adapter-enforced (the adapter controls routing). You can opt into
database-enforced isolation by giving each tenant a dedicated Postgres role with USAGE on only
its own schema - then the database itself blocks cross-schema access:
const tenancy = createKnexTenancy({
manager,
knex,
strategy: "schemaPerTenant",
schema: (tenant) => `tenant_${tenant.id}`,
role: (tenant) => `tenant_${tenant.id}_role`, // database-enforced
});Provisioning
Each tenant needs its schema created (and migrated) before use. Do that through the CLI's provisioning hooks - you supply how to create the schema and run your migrator; the CLI orchestrates per tenant.
npx tenancy tenant provision acme
npx tenancy tenant migrate acmeNever implement Prisma schema routing with session search_path; use createPrismaSchemaTenancy
with new PrismaPg({ connectionString }, { schema }). Shared credentials are adapter-routed; use a
schema-restricted role when the database itself must reject sibling-schema access.