tenant_id column. Import your schema and split your data into isolated tenant databases.Not sure which concepts mean what? Read the Overview first, then come back here.
$ tdb signup --email [email protected] --password ******** ✓ Account created! ✓ API key saved to ~/.tenantsdb.json
Your API key is saved automatically. Every CLI command from here uses it. You can also pass it as a header (Authorization: Bearer $API_KEY) when using the API directly.
TenantsDB workspaces come in two modes. Control mode gives you a managed database with full DDL access, no blueprints, no versioning, no tenant deployment. Just a database your application owns directly. Tenant mode tracks schema changes as blueprints and deploys them to isolated tenant databases.
$ tdb workspaces create --name controlplane --database PostgreSQL --mode control ✓ Workspace 'controlplane' created (control mode) ✓ Connection: postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/controlplane_workspace?sslmode=require
No blueprints or versioning
Schema changes apply immediately
Your app manages the schema
Deploy to isolated tenant databases
Schema versioned automatically
TenantsDB manages the schema
$ tdb workspaces create --name orders --database PostgreSQL --mode tenant ✓ Workspace 'orders' created ✓ Connection: postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/orders_workspace?sslmode=require
This workspace is in tenant mode (the default). Every schema change you make here is tracked automatically as a blueprint. When you create tenants later, they each get an isolated database with this schema.
-- Connect to controlplane_workspace CREATE TABLE users ( id SERIAL PRIMARY KEY, email TEXT UNIQUE NOT NULL, tenant_id TEXT NOT NULL, plan TEXT DEFAULT 'free' ); CREATE TABLE subscriptions ( id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id), plan TEXT NOT NULL, status TEXT DEFAULT 'active' );
-- Connect to orders_workspace CREATE TABLE orders ( id SERIAL PRIMARY KEY, product TEXT NOT NULL, amount DECIMAL(10,2), created_at TIMESTAMP DEFAULT NOW() );
The control workspace accepts your DDL directly, schema changes apply immediately. The tenant workspace tracks every DDL change as a blueprint version, ready to deploy.
$ tdb tenants create --name acme --blueprint orders ✓ Tenant 'acme' created ✓ Connection: postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/orders__acme?sslmode=require $ tdb tenants create --name globex --blueprint orders ✓ Tenant 'globex' created ✓ Connection: postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/orders__globex?sslmode=require
That's it. You now have a control database for your app logic, and isolated databases for each customer. Skip ahead to Your Setup to see how it all connects.
tenant_id column. Import your schema and split your data.$ tdb workspaces create --name myapp --database PostgreSQL --mode tenant ✓ Workspace 'myapp' created ✓ Connection: postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/myapp_workspace?sslmode=require
Schema import reads the structure (tables, columns, indexes, constraints) from your existing database and creates matching objects in the workspace. No data is copied at this step, just the structure.
$ tdb workspaces schema myapp --database '{ "type": "postgresql", "host": "your-db.example.com", "port": 5432, "database": "production", "user": "admin", "password": "secret" }' ✓ Schema imported: 8 tables, 3 indexes
You can also import from a JSON definition or a template. See the API Reference for all options.
This is where TenantsDB does the heavy lifting. You tell it which column identifies the tenant (typically tenant_id), and it splits your data into separate databases, one per tenant.
tenant_id ↓id=3 amount=150
Preview what will happen before committing. TenantsDB scans your source database, finds distinct tenant values, and shows you the breakdown.
$ tdb workspaces import-analyze myapp \
--host your-db.example.com \
--port 5432 \
--database production \
--user admin \
--password secret \
--routing-field tenant_id
Tenants found: acme, globex, wayne (3 total)
Tables: users (12,450 rows), orders (89,230 rows), products (3,100 rows)
All tables have routing field: ✓
Run the full import. TenantsDB creates a tenant for each distinct value in your routing field and copies the matching rows into their isolated database.
$ tdb workspaces import-data myapp \
--host your-db.example.com \
--port 5432 \
--database production \
--user admin \
--password secret \
--routing-field tenant_id \
--create-tenants \
--drop-routing
✓ Import started (ID: imp_a1b2c3)
Track progress: tdb workspaces import-status myapp --import-id imp_a1b2c3
$ tdb workspaces import-status myapp --import-id imp_a1b2c3 Status: importing (67%) acme: done (38,200 rows) globex: in progress (22,100 / 31,400 rows) wayne: pending
After migrating your tenant data, you have two options for your application's own database (users, billing, config):
Everything managed in one place.
Just use TenantsDB for tenant databases.
If you choose Option A:
$ tdb workspaces create --name controlplane --database PostgreSQL --mode control ✓ Workspace 'controlplane' created (control mode)
You can also import your existing app schema into the control workspace using schema import. The same command works for both modes. Then migrate your app data at your own pace.
Use your existing database driver. Point the connection string at TenantsDB's proxy instead of your raw database. No SDK, no new dependencies.
// npm install pg — standard PostgreSQL driver, nothing new const { Pool } = require('pg') // Control plane — shared across all requests const controlDB = new Pool({ connectionString: 'postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/controlplane_workspace?sslmode=require' }) // Per-request — resolve tenant from auth const { rows } = await controlDB.query( 'SELECT tenant_id FROM users WHERE api_key = $1', [req.headers.apiKey] ) const tenantId = rows[0].tenant_id // Connect to their isolated database const tenantDB = new Pool({ connectionString: `postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/orders__${tenantId}?sslmode=require` }) // Query — no WHERE tenant_id needed, this DB only has their data const orders = await tenantDB.query( 'SELECT * FROM orders WHERE created_at > $1', [lastWeek] )
The example above uses PostgreSQL. The same pattern works for every database TenantsDB supports; swap the connection string and driver.
Use any driver for your language: mysql2, mongoose, ioredis, PyMySQL, pymongo, go-redis — they all work out of the box. See Connections for full examples.