Skip to content

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:

Terminal window
npx @usebetterdev/tenant-cli init --database-url $DATABASE_URL
npx @usebetterdev/tenant-cli init # prompts for DATABASE_URL

migrate — 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.

Terminal window
# Preview the SQL
npx @usebetterdev/tenant-cli migrate --dry-run
# Write to file
npx @usebetterdev/tenant-cli migrate -o ./migrations

The generated migration includes:

  • tenants table with id (UUID), name, slug (unique), created_at
  • tenant_id column on each tenant table (UUID, NOT NULL, references tenants)
  • ENABLE ROW LEVEL SECURITY and FORCE ROW LEVEL SECURITY on each table
  • RLS policy with USING and WITH CHECK clauses
  • set_tenant_id() trigger that auto-populates tenant_id on 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:

Terminal window
# Preview
npx @usebetterdev/tenant-cli add-table comments --dry-run
# Write to file
npx @usebetterdev/tenant-cli add-table comments -o ./migrations

After 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:

Terminal window
# Preview
npx @usebetterdev/tenant-cli generate --dry-run
# Write to file
npx @usebetterdev/tenant-cli generate -o schema/better-tenant.ts

check — verify database setup

Runs 10+ validations against your database to confirm RLS is correctly configured:

Terminal window
npx @usebetterdev/tenant-cli check --database-url $DATABASE_URL

Checks include:

  • tenants table exists with correct columns
  • tenant_id column exists on each tenant table
  • RLS is enabled and forced
  • Policies have correct USING and WITH CHECK clauses
  • set_tenant_id() trigger is attached
  • Session variable functions work correctly

seed — insert a test tenant

Creates a tenant record using runAsSystem (RLS bypass):

Terminal window
npx @usebetterdev/tenant-cli seed --name "Acme Corp" --database-url $DATABASE_URL

Typical workflow

  1. Run init to create better-tenant.config.json
  2. Run migrate -o ./migrations to generate the initial migration
  3. Apply it: psql $DATABASE_URL -f ./migrations/*_better_tenant.sql
  4. Run check to verify everything is set up correctly
  5. Later, when you add a new table: run add-table <name> -o ./migrations and 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