TenancyJS
GuidesProvisioning per ORM

Provisioning per ORM

Batteries-included recipes for creating and migrating each tenant's placement with your own ORM's tooling.

TenancyJS routes every query to the right tenant's placement and fails closed when there is no scope. What it deliberately does not do is run CREATE SCHEMA / CREATE DATABASE or your migrations for you - creating a tenant's storage is your ORM's job, run through provisioner hooks that the tenancy CLI drives. That's the one manual seam: batteries-included, but not zero-config.

These recipes fill that seam. Each one is a concrete, copy-adaptable implementation of the three hooks for one ORM, wired so tenancy tenant provision | migrate | deprovision just work. They're written to be followed by a human or an AI assistant - paste your TENANCY.md context (from tenancy init --ai-context) and hand your agent the recipe for your stack.

The shape of every recipe

Whatever the ORM, onboarding a tenant is the same four moves:

Record - store.create

Write the tenant row (id + its placement: schema name or database key). Covered once in Configuration → bring-your-own store; the recipes assume it's in place.

Provision - provisioner.provision

Create the placement: CREATE SCHEMA for schema-per-tenant, CREATE DATABASE for database-per-tenant. Row-level shares tables, so there's nothing to provision.

Migrate - provisioner.migrate

Bring the fresh placement up to your current schema by running your ORM's own migrator against it. This is the part that differs most per ORM - it's the heart of each recipe.

Route - the adapter

At request time the adapter leases the tenant's connection and scopes the query. That's configured on the adapter page for your ORM; nothing extra to do here.

Then prove it with tenancy test:leak before you trust it.

Pick your ORM

ORMSchema-per-tenantDatabase-per-tenantRecipe
PrismaPostgreSQLPostgreSQL · MySQLPrisma →
KnexPostgreSQLPostgreSQLKnex →
LucidPostgreSQLPostgreSQLLucid →
TypeORMPostgreSQLPostgreSQL · MySQLTypeORM →
SequelizePostgreSQLPostgreSQL · MySQLSequelize →
DrizzlePostgreSQLPostgreSQL · MySQLDrizzle →
Mongoose-MongoDBMongoose →

MySQL has no schema namespace separate from a database (SCHEMA is a synonym for DATABASE), so on MySQL the per-tenant isolation is database-per-tenant. MongoDB likewise isolates per database.

The DDL and migrator calls in these recipes run with elevated privileges (a maintenance/admin connection that can create schemas or databases). Keep that connection separate from your fail-closed runtime role - the runtime role must not own tables or bypass RLS.

On this page