Build with AI
Copy a prompt, paste it into your AI coding assistant, and let it wire up fail-closed tenant isolation — pointed at the exact docs and the rules that stop it from leaking.
Adding tenant isolation is mechanical but easy to get subtly wrong — the dangerous mistakes are the ones that don't throw. These prompts hand your AI assistant (Claude, Cursor, Copilot, Windsurf, …) the exact docs to read and the fail-closed rules to follow, so it builds the wiring for your stack instead of guessing a leaky shortcut.
How to use it: pick your framework + ORM + strategy, copy the universal prompt,
fill the {{placeholders}} using the reference table, and paste it into an
assistant that can fetch URLs. Then review the result and run the two-tenant test it writes — with
fail-closed isolation, you verify, you don't trust.
The universal prompt
Works for any framework × ORM × strategy. Replace every {{placeholder}} (the reference table
tells you what to put where), then paste.
You are adding fail-closed multi-tenancy to my app with TenancyJS (https://tenancyjs.pages.dev).
My stack:
- Framework: {{FRAMEWORK — Express | Next.js | NestJS | AdonisJS}}
- ORM: {{ORM — Prisma | Knex | Lucid | TypeORM | Sequelize | Drizzle | Mongoose}}
- Database: {{DATABASE — PostgreSQL | MySQL | MongoDB}}
- Strategy: {{STRATEGY — row-level | schema-per-tenant | database-per-tenant}}
- Resolve the tenant from: {{TENANT SOURCE — subdomain | header X-Tenant-Id | JWT claim | path param}}
Before writing ANY code, fetch and read these docs. They are the source of truth — do not guess the API:
- https://tenancyjs.pages.dev/docs (the mental model: strategy × adapter × integration, and THE RULES)
- https://tenancyjs.pages.dev/docs/getting-started/quickstart (end-to-end wiring)
- https://tenancyjs.pages.dev/docs/adapters/{{ORM_SLUG}} (the exact adapter API for my ORM)
- https://tenancyjs.pages.dev/docs/integrations/{{FRAMEWORK_SLUG}} (binding tenant context to each request)
- https://tenancyjs.pages.dev/docs/strategies/{{STRATEGY_SLUG}} (how my strategy is enforced)
- https://tenancyjs.pages.dev/docs/guides/resolving-tenants (resolving the tenant from a request)
- https://tenancyjs.pages.dev/docs/concepts/limitations (what is REJECTED and must NOT be used)
- https://tenancyjs.pages.dev/docs/guides/testing-isolation (how to prove isolation)
Install (beta channel):
npm install tenancyjs-core@beta tenancyjs-adapter-{{ORM_SLUG}}@beta tenancyjs-integration-{{FRAMEWORK_PKG}}@beta {{ORM_PEER}}
Then build, in this order:
1. Create ONE TenancyManager<Tenant> and export it.
2. Create the {{ORM}} adapter with strategy "{{STRATEGY}}", registering my tenant-scoped models/tables.
Get the exact factory name and options from the adapter doc above.
3. Wire the {{FRAMEWORK}} integration so every request resolves the tenant from {{TENANT SOURCE}} and
opens a tenant scope (middleware / provider / config — see the integration doc).
4. Convert my data access so every tenant query runs through the scoped client the adapter gives me
inside a scope. Remove manual "WHERE tenant_id = ?" filters — the adapter injects them.
5. For admin / cross-tenant work, use the CENTRAL scope explicitly (see the integration doc). Never run
tenant-aware queries with no scope.
Follow these rules EXACTLY — this is the entire point, do not weaken them:
- Fail-closed: outside a valid tenant scope, tenant-aware access MUST throw. Do not add any fallback that
returns unscoped data.
- Never use the native ORM client / model / connection / collection inside a scope — it bypasses the
facade (a direct cross-tenant leak on MySQL/MongoDB, which have no database backstop). Use only the
scoped client.
- Raw SQL and nested/relational writes are REJECTED by the facade. They are safe ONLY in a
database-per-tenant scope, via the adapter's `unrestricted()` escape hatch (see the adapter doc). Do
not work around the facade any other way.
- Only registered models/tables and plain scalar filter criteria are allowed.
Deliver:
- The tenancy config/module, the per-request wiring, and the converted data-access code.
- A two-tenant test: create tenant A and tenant B with the SAME primary key, write a row under each, and
assert that querying as A never returns B's row — and that tenant-aware access with no scope throws.
Follow the testing-isolation guide.
If any detail isn't in the docs above, FETCH the linked page — never invent an API name.Fill-in reference
Use these to replace the placeholders.
Framework
| Framework | {{FRAMEWORK_SLUG}} (docs) | {{FRAMEWORK_PKG}} (package) |
|---|---|---|
| Express | express | express |
| Next.js | nextjs | next |
| NestJS | nestjs | nest |
| AdonisJS | adonis | adonis |
The docs slug and the package suffix differ for Next.js and NestJS — the doc is /integrations/nextjs
but the package is tenancyjs-integration-next; the doc is /integrations/nestjs but the package is
tenancyjs-integration-nest. Use each column as labelled.
ORM adapter
| ORM | {{ORM_SLUG}} | {{ORM_PEER}} (peer to install) | Strategies |
|---|---|---|---|
| Prisma | prisma | @prisma/client | row-level, schema-per-tenant, database-per-tenant |
| Knex | knex | knex | all three (PostgreSQL) |
| Lucid | lucid | @adonisjs/lucid | all three on PostgreSQL; database-per-tenant on MySQL |
| TypeORM | typeorm | typeorm + pg/mysql2 | row-level, schema-per-tenant, database-per-tenant |
| Sequelize | sequelize | sequelize + pg/mysql2 | row-level, schema-per-tenant, database-per-tenant |
| Drizzle | drizzle | drizzle-orm | row-level, schema-per-tenant, database-per-tenant |
| Mongoose | mongoose | mongoose | row-level, database-per-tenant (MongoDB) |
{{STRATEGY_SLUG}} is row-level, schema-per-tenant, or database-per-tenant.
Lucid pairs with AdonisJS (tenancyjs-integration-adonis). Schema-per-tenant is PostgreSQL-only,
and MySQL row-level is experimental — the capability matrix
shows exactly what's tested for your database.
Ready-made examples
Fully-filled prompts you can copy as-is (or adapt).
Express + Prisma, row-level on PostgreSQL
You are adding fail-closed multi-tenancy to my Express + Prisma app on PostgreSQL with TenancyJS,
using the row-level strategy. Resolve the tenant from the subdomain.
Read first (source of truth — do not guess the API):
- https://tenancyjs.pages.dev/docs (mental model + THE RULES)
- https://tenancyjs.pages.dev/docs/getting-started/quickstart
- https://tenancyjs.pages.dev/docs/adapters/prisma
- https://tenancyjs.pages.dev/docs/integrations/express
- https://tenancyjs.pages.dev/docs/strategies/row-level
- https://tenancyjs.pages.dev/docs/guides/resolving-tenants
- https://tenancyjs.pages.dev/docs/concepts/limitations (what is REJECTED)
- https://tenancyjs.pages.dev/docs/guides/testing-isolation
Install: npm install tenancyjs-core@beta tenancyjs-adapter-prisma@beta tenancyjs-integration-express@beta @prisma/client
Build: (1) one exported TenancyManager<Tenant>; (2) the Prisma adapter with strategy "rowLevel",
registering my tenant models; (3) Express middleware that resolves the tenant from req.subdomains and
opens the scope; (4) route all tenant queries through the scoped client (drop manual tenant filters);
(5) central scope for admin work.
Rules (do not weaken): fail-closed — unscoped tenant access must throw; never touch the native Prisma
client inside a scope; raw/nested writes are rejected by the facade; registered models + scalar filters
only. On PostgreSQL row-level, also add the forced RLS policy the adapter doc describes.
Deliver the config, the middleware, the converted routes, and a two-tenant colliding-id test proving no
cross-tenant read and that unscoped access throws. Fetch any linked page rather than inventing an API.AdonisJS + Lucid, schema-per-tenant on PostgreSQL
You are adding fail-closed multi-tenancy to my AdonisJS + Lucid app on PostgreSQL with TenancyJS, using
the schema-per-tenant strategy (one Postgres schema per tenant). Resolve the tenant from a header.
Read first (source of truth — do not guess the API):
- https://tenancyjs.pages.dev/docs (mental model + THE RULES)
- https://tenancyjs.pages.dev/docs/getting-started/quickstart
- https://tenancyjs.pages.dev/docs/adapters/lucid
- https://tenancyjs.pages.dev/docs/integrations/adonis
- https://tenancyjs.pages.dev/docs/strategies/schema-per-tenant
- https://tenancyjs.pages.dev/docs/concepts/limitations (what is REJECTED)
- https://tenancyjs.pages.dev/docs/guides/testing-isolation
Tip: `npx tenancy init` scaffolds AdonisJS + Lucid end to end — use it, then adapt.
Install: npm install tenancyjs-core@beta tenancyjs-adapter-lucid@beta tenancyjs-integration-adonis@beta
Build: (1) config/tenancy.ts with a TenancyManager<Tenant> and the Lucid adapter, strategy
"schemaPerTenant", schema: (tenant) => `tenant_${tenant.id}`; (2) register the integration provider +
middleware; (3) my Lucid models are then scoped automatically — no per-query tenant filters; (4) use the
central context for admin work.
Rules (do not weaken): fail-closed — unscoped tenant access must throw; never reach for the raw Lucid
connection inside a scope; raw queries and nested writes are rejected on the facade (they only work in a
database-per-tenant scope via scope.unrestricted()); registered models + scalar filters only.
Deliver config/tenancy.ts, the provider/middleware registration, and a two-tenant colliding-id test
(two schemas) proving no cross-tenant read and that unscoped access throws. Fetch any linked page rather
than inventing an API.AI-generated wiring is a starting point, not proof. Always run the two-tenant colliding-id test the prompt asks for — that's the only thing that actually demonstrates isolation. See Testing isolation and the honest Limitations.