CLI & Migrations
The CLI (@usebetterdev/tenant-cli) handles database setup for multi-tenancy. It generates SQL migrations, verifies your RLS configuration, and seeds test data.
Configuration
The CLI reads from better-tenant.config.json in your project root (or from the "betterTenant" key in package.json):
{ "tenantTables": ["projects", "tasks"]}tenantTables lists the tables that should be tenant-scoped. The CLI adds a tenant_id column, RLS policies, and triggers to each one.
Commands
init — create config interactively
Connects to your database, detects tables, and creates better-tenant.config.json:
npx @usebetterdev/tenant-cli init --database-url $DATABASE_URLnpx @usebetterdev/tenant-cli init # prompts for DATABASE_URLmigrate — initial setup
Generates a single SQL migration that creates the tenants table and adds RLS to all tables in tenantTables. Run this once when setting up multi-tenancy.
# Preview the SQLnpx @usebetterdev/tenant-cli migrate --dry-run
# Write to filenpx @usebetterdev/tenant-cli migrate -o ./migrationsThe generated migration includes:
tenantstable withid(UUID),name,slug(unique),created_attenant_idcolumn on each tenant table (UUID, NOT NULL, referencestenants)ENABLE ROW LEVEL SECURITYandFORCE ROW LEVEL SECURITYon each table- RLS policy with
USINGandWITH CHECKclauses set_tenant_id()trigger that auto-populatestenant_idon INSERT
add-table — add a table later
Generates SQL for a single table that wasn’t in your original migration. Use this when you create a new table after initial setup:
# Previewnpx @usebetterdev/tenant-cli add-table comments --dry-run
# Write to filenpx @usebetterdev/tenant-cli add-table comments -o ./migrationsAfter running add-table, add the table name to tenantTables in your config.
generate — Drizzle schema snippet
Generates a Drizzle schema file for the tenants table:
# Previewnpx @usebetterdev/tenant-cli generate --dry-run
# Write to filenpx @usebetterdev/tenant-cli generate -o schema/better-tenant.tscheck — verify database setup
Runs 10+ validations against your database to confirm RLS is correctly configured:
npx @usebetterdev/tenant-cli check --database-url $DATABASE_URLChecks include:
tenantstable exists with correct columnstenant_idcolumn exists on each tenant table- RLS is enabled and forced
- Policies have correct
USINGandWITH CHECKclauses set_tenant_id()trigger is attached- Session variable functions work correctly
seed — insert a test tenant
Creates a tenant record using runAsSystem (RLS bypass):
npx @usebetterdev/tenant-cli seed --name "Acme Corp" --database-url $DATABASE_URLTypical workflow
- Run
initto createbetter-tenant.config.json - Run
migrate -o ./migrationsto generate the initial migration - Apply it:
psql $DATABASE_URL -f ./migrations/*_better_tenant.sql - Run
checkto verify everything is set up correctly - Later, when you add a new table: run
add-table <name> -o ./migrationsand apply it
Programmatic API
The CLI exports functions for use in scripts or custom tooling:
import { generateMigrationSql } from "@usebetterdev/tenant-cli/migrate";import { runCheck } from "@usebetterdev/tenant-cli/check";import { runSeed } from "@usebetterdev/tenant-cli/seed";Next steps
- Configuration — resolver strategies, tenant API, and admin operations
- Framework Adapters — per-adapter setup for Drizzle, Prisma, Hono, Express, and Next.js
- Architecture — how the generated SQL and RLS policies work under the hood