Skip to content

Database Schema

Alokai CMS uses Cloudflare D1 (SQLite) as its primary database. Migrations live in src/db/migrations/.

Tables

organizations

Top-level multi-tenancy container.

CREATE TABLE organizations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

spaces

Content silo within an organization.

CREATE TABLE spaces (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL REFERENCES organizations(id),
name TEXT NOT NULL,
slug TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

environments

Deployment stage within a space.

CREATE TABLE environments (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL REFERENCES spaces(id),
name TEXT NOT NULL,
description TEXT,
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

users

CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT,
password_hash TEXT,
role TEXT NOT NULL DEFAULT 'viewer',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

Global roles: super_admin, admin, editor, viewer.

org_members / space_members

CREATE TABLE org_members (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL REFERENCES organizations(id),
user_id TEXT NOT NULL REFERENCES users(id),
role TEXT NOT NULL, -- owner | admin | developer | member
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE space_members (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL REFERENCES spaces(id),
user_id TEXT NOT NULL REFERENCES users(id),
role TEXT NOT NULL, -- admin | editor | author
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

content_models

CREATE TABLE content_models (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
name TEXT NOT NULL,
api_id TEXT NOT NULL,
description TEXT,
fields TEXT NOT NULL DEFAULT '[]', -- JSON array of field definitions
is_builtin INTEGER NOT NULL DEFAULT 0,
duplicated_from TEXT,
created_by TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

pages

CREATE TABLE pages (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
content_type TEXT NOT NULL, -- page | categoryPage | productPage | layout
path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
published_version INTEGER,
scheduled_at TEXT,
layout_id TEXT REFERENCES pages(id),
is_default_layout INTEGER NOT NULL DEFAULT 0,
created_by TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

page_versions

Immutable version snapshots.

CREATE TABLE page_versions (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
page_id TEXT NOT NULL REFERENCES pages(id),
version INTEGER NOT NULL,
locale TEXT NOT NULL DEFAULT 'en-US',
data TEXT NOT NULL DEFAULT '{}', -- JSON page content
created_by TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

components / component_versions

Same structure as pages/page_versions but for reusable components.

assets

CREATE TABLE assets (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
filename TEXT NOT NULL,
content_type TEXT NOT NULL,
size INTEGER NOT NULL,
r2_key TEXT NOT NULL,
alt_text TEXT,
width INTEGER,
height INTEGER,
created_by TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

webhooks

CREATE TABLE webhooks (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
events TEXT NOT NULL DEFAULT '[]', -- JSON string array
headers TEXT NOT NULL DEFAULT '{}', -- JSON object
is_active INTEGER NOT NULL DEFAULT 1,
secret TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

api_keys

CREATE TABLE api_keys (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
prefix TEXT NOT NULL,
key_hash TEXT NOT NULL UNIQUE,
permissions TEXT NOT NULL DEFAULT '[]', -- JSON string array
created_by TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_used_at TEXT
);

locales

CREATE TABLE locales (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
code TEXT NOT NULL, -- e.g. en-US
name TEXT NOT NULL,
is_default INTEGER NOT NULL DEFAULT 0
);

preview_urls

CREATE TABLE preview_urls (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
environment_id TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

audit_log

CREATE TABLE audit_log (
id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
user_id TEXT,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
details TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

Migrations

Migrations live in src/db/migrations/ and are numbered sequentially:

FileDescription
001_initial.sqlCore tables
002_seed.sqlDefault org, space, env, user
003_rename_env_master.sqlRename master env to main
004_api_keys.sqlAdd api_keys table
005_api_keys_v2.sqlAdd permissions column
006_layouts.sqlAdd layout_id and is_default_layout to pages

Run locally: yarn db:migrate Run on remote: wrangler d1 migrations apply alokon-db --remote