TenancyJS
Isolation Strategies

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 acme

Never 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.

On this page