Skip to content

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>
FieldValue
Package@odp/api
Binaryodp
FrameworkCommander.js
Sourceservice/api/src/cli/

Commands

odp start

Start the ODP API server.

bash
odp start

Imports 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 bootstrap

Steps:

  1. Connect to database (using env vars DB_CLIENT, DB_HOST, etc.)
  2. Run all pending migrations
  3. Seed default data (admin role, admin user — credentials printed on first run)
  4. 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
OptionDescription
--downRollback 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
OptionDescription
--forceRequired. 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
OptionDescriptionDefault
-f, --format <format>Output format: yaml or jsonyaml
-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 / OptionDescription
<path>Path to the schema snapshot file (YAML or JSON, auto-detected by extension)
--dry-runPrint 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.author

Schema 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.yaml

odp 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
OptionDescriptionRequired
--email <email>User email addressYes
--password <password>User password (hashed with argon2 before storing)Yes
--role <roleId>Role UUID to assignNo
--adminGrant 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"
OptionDescriptionRequired
--email <email>User emailYes
--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
OptionDescriptionRequired
--name <name>Role display nameYes
--adminGrant admin accessNo
--appGrant app (frontend) accessNo

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
OptionDescriptionDefault
-n, --name <name>Extension folder nameRequired
-t, --type <type>Extension type: hook, endpoint, or bundlebundle

Extension Types

TypeDescriptionGenerated Structure
hookEvent hooks (item CRUD, auth, etc.)src/index.ts with defineHook
endpointCustom API endpointssrc/index.ts with defineEndpoint
bundleCombined hooks + endpointssrc/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.json

Hook / Endpoint type:

extensions/my-extension/
├── src/
│   └── index.ts              # defineHook or defineEndpoint
├── package.json
└── tsconfig.json

The 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
OptionDescription
-n, --name <name>Build only this extension (folder name)

The build process:

  1. Scans EXTENSIONS_PATH (default: ./extensions) for folders with package.json containing odp-extension metadata
  2. Installs dependencies (npm install) if node_modules/ doesn't exist
  3. Runs npm run build for each extension

Environment Variables

The CLI commands that access the database use the same environment variables as the API server:

VariableDescriptionExample
DB_CLIENTDatabase clientpg, mysql2, sqlite3
DB_HOSTDatabase hostlocalhost
DB_PORTDatabase port5432
DB_DATABASEDatabase nameodp
DB_USERDatabase userodp_user
DB_PASSWORDDatabase password
EXTENSIONS_PATHPath to extensions directory./extensions

See Environment Variables for the full list.


Quick Reference

CommandPurpose
odp startStart API server
odp bootstrapInitialize DB (migrate + seed)
odp database:migrateRun pending migrations
odp database:migrate --downRollback last migration
odp database:install --forceDrop & recreate all tables
odp schema:snapshotExport schema to file
odp schema:apply <path>Apply schema from snapshot
odp users:createCreate a user
odp users:passwdChange user password
odp roles:createCreate a role
odp extension:createScaffold a new extension
odp extension:buildBuild extensions

ODP Internal API Documentation