HelloJohn / docs
Architecture

Database Schema

HelloJohn's database schema — global tables for the Control Plane and per-tenant tables for the Data Plane. Entity relationships, primary keys, and key indexes.

HelloJohn uses two database contexts: the global database (Control Plane) and per-tenant databases (Data Plane). Both are managed by embedded migrations that run automatically on startup.

Global database (Control Plane)

The global database stores tenant and operator-level data.

cp_tenant

Stores all tenants registered in this HelloJohn instance.

ColumnTypeDescription
idtext PKTenant ID: tnt_01HABCDEF...
nametextHuman-readable tenant name
slugtext UNIQUEURL-safe identifier
plantextfree, starter, pro, enterprise
statustextactive, suspended, deleted
db_dsntextConnection string for this tenant's DB (encrypted)
created_attimestamptz

cp_client

OAuth2 clients (applications) registered under a tenant.

ColumnTypeDescription
idtext PKClient ID: cli_01HABCDEF...
tenant_idtext FK → cp_tenant.id
nametextClient name
typetextweb, mobile, api
publishable_keytext UNIQUEpk_live_... — safe for browser
secret_key_hashtextHashed sk_live_... — never stored plaintext
redirect_uristext[]Allowed OAuth2 redirect URIs
created_attimestamptz

cp_admin

Control Plane admin accounts.

ColumnTypeDescription
idtext PKAdmin ID: adm_01HABCDEF...
emailtext UNIQUE
password_hashtextArgon2id hash
roletextsuper_admin, admin
created_attimestamptz

Per-tenant database (Data Plane)

Each tenant has its own database (or schema) with the following tables.

hj_user

End users of the tenant's application.

ColumnTypeDescription
idtext PKUser ID: usr_01HABCDEF...
emailtext UNIQUE
email_verifiedboolean
nametextDisplay name
avatar_urltext
password_hashtextArgon2id hash (null if OAuth-only)
roletextmember, admin, super_admin
public_metadatajsonbIncluded in JWT
private_metadatajsonbNOT included in JWT
statustextactive, suspended, deleted
created_attimestamptz
updated_attimestamptz

hj_session

Active user sessions.

ColumnTypeDescription
idtext PKSession ID: ses_01HABCDEF...
user_idtext FK → hj_user.id
refresh_token_hashtextHash of the opaque refresh token
user_agenttextBrowser/client user agent
ip_addressinetClient IP at sign-in
last_active_attimestamptzUpdated on each token refresh
expires_attimestamptz
revoked_attimestamptzSet on logout
created_attimestamptz

hj_mfa_factor

Enrolled MFA factors per user.

ColumnTypeDescription
idtext PKFactor ID: mfa_01HABCDEF...
user_idtext FK → hj_user.id
typetexttotp, webauthn, sms, email
nametextUser-facing label (e.g. "iPhone 14")
secrettextEncrypted TOTP secret / WebAuthn credential
backup_codes_hashtext[]Hashed backup codes
verified_attimestamptzFirst successful verification
last_used_attimestamptz
created_attimestamptz

hj_oauth_provider

Linked OAuth providers per user.

ColumnTypeDescription
idtext PK
user_idtext FK → hj_user.id
providertextgoogle, github, apple, etc.
provider_user_idtextUser ID from the OAuth provider
access_tokentextEncrypted OAuth access token (if stored)
created_attimestamptz

hj_organization

Organizations within a tenant (B2B workspaces).

ColumnTypeDescription
idtext PKOrg ID: org_01HABCDEF...
nametext
slugtext UNIQUE
metadatajsonbCustom org data
created_attimestamptz

hj_org_member

Membership records linking users to organizations.

ColumnTypeDescription
idtext PK
org_idtext FK → hj_organization.id
user_idtext FK → hj_user.id
roletextowner, admin, member, guest
joined_attimestamptz

Entity relationship diagram

cp_tenant (1) ──< cp_client (many)

cp_tenant (1) ──[per-tenant DB]──

hj_user (1) ──< hj_session (many)
hj_user (1) ──< hj_mfa_factor (many)
hj_user (1) ──< hj_oauth_provider (many)
hj_user (1) ──< hj_org_member (many)

hj_organization (1) ──< hj_org_member (many)

ID format

All HelloJohn IDs use a prefixed ULID format: {prefix}_{ulid}.

PrefixEntity
tnt_Tenant
cli_Client
adm_CP Admin
usr_User
ses_Session
mfa_MFA Factor
org_Organization

ULIDs are sortable by creation time, collision-resistant, and URL-safe.

Migrations

HelloJohn runs embedded SQL migrations automatically on startup. No manual ALTER TABLE commands needed. Migrations are additive-only — no destructive schema changes in patch releases.

# Check migration status
hjctl migrate status

# Run pending migrations manually
hjctl migrate up

# Roll back last migration (dev only)
hjctl migrate down

Always backup your database before upgrading HelloJohn to a new minor or major version.

Next steps

On this page