Appearance
CLI Reference
The ODP Service API ships a built-in CLI binary odp for server management, database operations, user administration, schema migration, and extension scaffolding.
Installation
The CLI is bundled with @odp/api. After building, the binary is available at ./dist/cli/index.js.
bash
# From the service/api directory
pnpm build
node dist/cli/index.js <command>
# Or via the bin alias (if linked / installed globally)
odp <command>| Field | Value |
|---|---|
| Package | @odp/api |
| Binary | odp |
| Framework | Commander.js |
| Source | service/api/src/cli/ |
Commands
odp start
Start the ODP API server.
bash
odp startImports and runs the main server entry point. Equivalent to running node dist/index.js directly.
odp bootstrap
Initialize the database: run all pending migrations and seed defaults.
bash
odp bootstrapSteps:
- Connect to database (using env vars
DB_CLIENT,DB_HOST, etc.) - Run all pending migrations
- Seed default data (admin role, admin user — credentials printed on first run)
- Close connection
Use this on first deployment or when setting up a fresh database.
odp database:migrate
Run pending database migrations, or rollback the last one.
bash
# Apply pending migrations
odp database:migrate
# Rollback last migration
odp database:migrate --down| Option | Description |
|---|---|
--down | Rollback the last applied migration instead of migrating forward |
odp database:install
Drop all ODP system tables and recreate from scratch. Destructive operation.
bash
odp database:install --force| Option | Description |
|---|---|
--force | Required. Skip confirmation prompt. Without this flag, the command exits immediately. |
DANGER
This drops all odp_* system tables (in reverse order to respect foreign keys) and re-runs all migrations. All data in system tables will be lost.
odp schema:snapshot
Export the current database schema to a file.
bash
# Default: YAML format, writes to ./schema-snapshot.yaml
odp schema:snapshot
# JSON format
odp schema:snapshot --format json
# Custom output path
odp schema:snapshot -o ./snapshots/prod-2025-01-15.yaml| Option | Description | Default |
|---|---|---|
-f, --format <format> | Output format: yaml or json | yaml |
-o, --output <path> | Output file path | ./schema-snapshot.{format} |
The snapshot captures all collections, fields, and relations — including metadata needed to reproduce the schema on another instance.
odp schema:apply
Apply schema changes from a snapshot file. Computes a diff between the snapshot and the current database, then applies the changes.
bash
# Apply from YAML snapshot
odp schema:apply ./snapshots/prod-schema.yaml
# Dry run — show changes without applying
odp schema:apply ./snapshots/prod-schema.yaml --dry-run| Argument / Option | Description |
|---|---|
<path> | Path to the schema snapshot file (YAML or JSON, auto-detected by extension) |
--dry-run | Print the diff without applying changes |
Diff output shows changes with prefixes:
+ CREATE collection: my_collection
+ ADD field: my_collection.title
~ UPDATE field: my_collection.status
- DROP field: my_collection.old_field
+ CREATE relation: my_collection.authorSchema Promotion Workflow
bash
# 1. Export schema from staging
odp schema:snapshot -o staging-schema.yaml
# 2. Review changes on production (dry run)
odp schema:apply staging-schema.yaml --dry-run
# 3. Apply to production
odp schema:apply staging-schema.yamlodp users:create
Create a new user in the database.
bash
# Create a regular user
odp users:create --email user@example.com --password "s3cret"
# Create a user with a specific role
odp users:create --email user@example.com --password "s3cret" --role <role-uuid>
# Create an admin user (auto-assigns admin role)
odp users:create --email admin@example.com --password "s3cret" --admin| Option | Description | Required |
|---|---|---|
--email <email> | User email address | Yes |
--password <password> | User password (hashed with argon2 before storing) | Yes |
--role <roleId> | Role UUID to assign | No |
--admin | Grant admin access (finds and assigns the admin role automatically) | No |
The user is created with status: active. If --admin is used without --role, the CLI looks up the first role with admin_access: true.
odp users:passwd
Change an existing user's password.
bash
odp users:passwd --email user@example.com --password "newP@ss"| Option | Description | Required |
|---|---|---|
--email <email> | User email | Yes |
--password <password> | New password (hashed with argon2) | Yes |
odp roles:create
Create a new role.
bash
# Basic role (no special access)
odp roles:create --name "Editor"
# Role with admin access
odp roles:create --name "Super Admin" --admin
# Role with app access only
odp roles:create --name "Viewer" --app| Option | Description | Required |
|---|---|---|
--name <name> | Role display name | Yes |
--admin | Grant admin access | No |
--app | Grant app (frontend) access | No |
odp extension:create
Scaffold a new API extension with boilerplate code and configuration.
bash
# Create a bundle extension (default)
odp extension:create -n my-extension
# Create a specific type
odp extension:create -n my-hooks -t hook
odp extension:create -n my-api -t endpoint| Option | Description | Default |
|---|---|---|
-n, --name <name> | Extension folder name | Required |
-t, --type <type> | Extension type: hook, endpoint, or bundle | bundle |
Extension Types
| Type | Description | Generated Structure |
|---|---|---|
hook | Event hooks (item CRUD, auth, etc.) | src/index.ts with defineHook |
endpoint | Custom API endpoints | src/index.ts with defineEndpoint |
bundle | Combined hooks + endpoints | src/hooks/index.ts + src/endpoints/index.ts |
Generated Files
Bundle type (default):
extensions/my-extension/
├── src/
│ ├── index.ts # Re-exports hooks + endpoints
│ ├── hooks/index.ts # Hook with defineHook
│ └── endpoints/index.ts # Endpoint with defineEndpoint
├── package.json # odp-extension metadata + build scripts
└── tsconfig.jsonHook / Endpoint type:
extensions/my-extension/
├── src/
│ └── index.ts # defineHook or defineEndpoint
├── package.json
└── tsconfig.jsonThe generated package.json includes an odp-extension key used by the extension loader:
json
{
"odp-extension": {
"id": "my-extension",
"type": "bundle",
"entries": [
{ "type": "hook", "name": "my-extension-hooks", "source": "src/hooks/index.ts" },
{ "type": "endpoint", "name": "my-extension-endpoints", "source": "src/endpoints/index.ts" }
]
}
}odp extension:build
Build TypeScript extensions (compile src/ → dist/).
bash
# Build all extensions
odp extension:build
# Build a specific extension
odp extension:build -n my-extension| Option | Description |
|---|---|
-n, --name <name> | Build only this extension (folder name) |
The build process:
- Scans
EXTENSIONS_PATH(default:./extensions) for folders withpackage.jsoncontainingodp-extensionmetadata - Installs dependencies (
npm install) ifnode_modules/doesn't exist - Runs
npm run buildfor each extension
Environment Variables
The CLI commands that access the database use the same environment variables as the API server:
| Variable | Description | Example |
|---|---|---|
DB_CLIENT | Database client | pg, mysql2, sqlite3 |
DB_HOST | Database host | localhost |
DB_PORT | Database port | 5432 |
DB_DATABASE | Database name | odp |
DB_USER | Database user | odp_user |
DB_PASSWORD | Database password | |
EXTENSIONS_PATH | Path to extensions directory | ./extensions |
See Environment Variables for the full list.
Quick Reference
| Command | Purpose |
|---|---|
odp start | Start API server |
odp bootstrap | Initialize DB (migrate + seed) |
odp database:migrate | Run pending migrations |
odp database:migrate --down | Rollback last migration |
odp database:install --force | Drop & recreate all tables |
odp schema:snapshot | Export schema to file |
odp schema:apply <path> | Apply schema from snapshot |
odp users:create | Create a user |
odp users:passwd | Change user password |
odp roles:create | Create a role |
odp extension:create | Scaffold a new extension |
odp extension:build | Build extensions |