Choose Your Path
Two ways to get started. Both end at the same place: a control database for your app, and isolated databases for each customer.
Path 1
Fresh Start
New project, no existing database. Create your control plane and tenant schema from scratch.
Path 2
Migrate Existing Database
You have a shared database with a 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.


Sign Up
Both paths start here. One command to create your account.
Don't have the CLI yet? Install it first →
Shell
$ 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.


Path 1 · Fresh Start
New project, no existing database.
Create Your Control Workspace
Your app's backend database. Users, billing, config — everything that isn't per-tenant.

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.

Shell
$ 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
Control Mode
Full DDL access
No blueprints or versioning
Schema changes apply immediately
Your app manages the schema
Tenant Mode
DDL changes tracked as blueprints
Deploy to isolated tenant databases
Schema versioned automatically
TenantsDB manages the schema
Both modes get: TLS connections, proxy routing, query logging, settings enforcement, backups, API and CLI access.

Create Your Tenant Workspace
The schema every customer database will use.
Shell
$ 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.


Build Your Schemas
Connect to each workspace with any standard client and create your tables.
SQL — controlplane
-- 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'
);
SQL — orders (tenant)
-- 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.


Create Your First Tenants
Each tenant gets an isolated database with your schema deployed automatically.
Shell
$ 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.


Path 2 · Migrate Existing Database
You have a shared database with a tenant_id column. Import your schema and split your data.
Create a Tenant Workspace
This workspace will receive your imported schema and become the blueprint for all tenant databases.
Shell
$ 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

Import Your Schema
Pull your existing table structure into the workspace. Your source database is never modified.

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.

Shell
$ 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.

Schema import works for both tenant and control workspaces. For tenant workspaces, imported DDL appears in the diff queue like any other change. Deploy it to tenants when ready.

Import Data
Split your shared database into isolated tenant databases using a routing field.

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.

Before: shared database
orders table
id=1amount=100tenant_id=acme id=2amount=200tenant_id=globex id=3amount=150tenant_id=acme
↓   split by tenant_id   ↓
myapp__acme
id=1   amount=100
id=3   amount=150
myapp__globex
id=2   amount=200
Each tenant gets their own isolated database. The routing column is removed.
Step 1 — Analyze

Preview what will happen before committing. TenantsDB scans your source database, finds distinct tenant values, and shows you the breakdown.

Shell
$ 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: ✓
Step 2 — Import

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.

Shell
$ 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
Step 3 — Monitor
Shell
$ 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
Data import only works for tenant workspaces. It splits data by the routing field and creates isolated tenant databases. To import data into a control workspace, use schema import and then populate it directly through your application.

Add a Control Workspace
Optional. If you want TenantsDB to manage your app's backend database too.

After migrating your tenant data, you have two options for your application's own database (users, billing, config):

Option A: Create in TenantsDB
Same proxy, TLS, logging, and settings.
Everything managed in one place.
Option B: Keep your existing DB
Your app already has a backend database.
Just use TenantsDB for tenant databases.

If you choose Option A:

Shell
$ 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.


Your Setup
Both paths end here. A control database for your app, isolated databases for each customer.
Your TenantsDB Project
Control
controlplane
PostgreSQL · users, billing, config
Tenant
orders__acme
Tenant
orders__globex
Tenant
orders__wayne
Same project · Same API key · Same proxy
How a request flows
Request
Customer logs in
Control
resolves → "acme"
Tenant
orders__acme
Your app is the bridge. TenantsDB provides the databases.
Wire Up Your Application

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]
)
No SDK required. Both connections go through the TenantsDB proxy using your language's standard database driver. Your application never connects to raw database servers. All traffic is TLS-encrypted and logged.
Connection Strings by Database

The example above uses PostgreSQL. The same pattern works for every database TenantsDB supports; swap the connection string and driver.

PostgreSQL postgresql://{project_id}:{api_key}@pg.tenantsdb.com:5432/orders__acme?sslmode=require
MySQL mysql://{project_id}:{api_key}@mysql.tenantsdb.com:3306/orders__acme
MongoDB mongodb://{project_id}:{api_key}@mongo.tenantsdb.com:27017/orders__acme?authMechanism=PLAIN&directConnection=true&tls=true
Redis rediss://{tenant_id}:{api_key}@redis.tenantsdb.com:6379/0

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.