Skip to content

Extensions System

Overview

The ODP extensions system allows custom functionality to be loaded into the API at runtime. Extensions are stored in the filesystem under EXTENSIONS_PATH and registered in the odp_extensions database table.

Extensions can be enabled/disabled individually through the admin API. When enabled, the ExtensionManager loads the extension's code dynamically. When disabled, the extension's hooks are unregistered without requiring a server restart.


Extension Types

Extensions can provide:

  • API hooks — Register custom route handlers, middleware, and emitter listeners
  • App modules — Register new app permission modules
  • Custom endpoints — Add routes to the Fastify instance
  • Settings providers — Provide configuration settings to other parts of the system

Endpoints

GET /extensions

List all registered extensions.

Auth required: Yes (no specific permission required for listing)

Response:

json
{
  "data": [
    {
      "id": "extension-uuid",
      "enabled": true,
      "folder": "my-custom-extension",
      "source": "local",
      "bundle": null
    },
    {
      "id": "another-uuid",
      "enabled": false,
      "folder": "analytics-plugin",
      "source": "module",
      "bundle": null
    }
  ]
}
FieldDescription
idUUID
enabledWhether the extension is loaded
folderDirectory name under EXTENSIONS_PATH
sourceWhere the extension comes from: local, registry, module
bundleParent bundle UUID (if part of a bundled extension)

GET /extensions/:id

Get a single extension by ID.

Auth required: Yes

Response: Single extension object.

Error: 404 Not Found if extension does not exist.


Bật/tắt extension = cờ `enabled` + restart

Không còn route runtime enable/disable. Extensions chỉ load lúc boot (tôn trọng cờ enabled trong odp_extensions). Đổi trạng thái → sửa cờ rồi restart (dev) / redeploy (prod). Fastify không hỗ trợ gỡ/thay route runtime nên không thể reload endpoint in-process — xem Extension Development → Hot Reload.


Extension Settings

Extensions can store their configuration in odp_extension_settings. This is used by the auth providers and storage providers as well as custom extensions.

GET /extension-settings

List all extension settings (admin required).

Query Parameters:

  • extension_key — Filter by extension key

Response:

json
{
  "data": [
    {
      "id": 1,
      "extension_key": "system_auth_provider",
      "name": "google",
      "value": { "driver": "openid", "clientId": "...", "clientSecret": "..." }
    }
  ]
}

Extension Manager

The ExtensionManager singleton manages the extension lifecycle:

extensionManager.scanAndLoadHooks() → Phase 1: load hooks of all enabled extensions
extensionManager.loadEndpoints(app)  → Phase 2: mount endpoint routes

Extensions load once at boot (no runtime reload/unload).

Extension Loading

When loading an extension:

  1. Reads the extension record from odp_extensions
  2. Resolves the extension folder path: EXTENSIONS_PATH/{folder}
  3. Dynamically imports the extension's main module
  4. Passes ApiExtensionContext with services, database, getSchema, emitter, env, logger, validateAppAccess
  5. Extension registers its hooks, routes, and listeners using the provided context

Dev hot reload

Hot reload is a dev tooling concern, not part of the runtime: run the API with odp dev (≡ odp start --watch) alongside odp-extension build --watch — the server does a clean full restart after each successful rebuild. See Extension Development → Hot Reload.


Data Model

odp_extensions

ColumnTypeDescription
idUUID PK
enabledbooleanWhether the extension is active
folderstring(255)Directory name under EXTENSIONS_PATH
sourcestring(255)local, registry, or module
bundleUUID FK → odp_extensionsParent bundle (self-referential)

odp_extension_settings

ColumnTypeDescription
idinteger PK (auto-increment)
extension_keystringNamespace key (e.g., system_auth_provider, system_storage_provider)
namestringSetting name within the namespace
valuetext/JSONConfiguration value

Known extension keys:

  • system_auth_provider — SSO provider configurations
  • system_storage_provider — Storage driver configurations
  • system_mcp_settings — MCP server settings

Configuration

VariableDefaultDescription
EXTENSIONS_PATH./extensionsDirectory for extension folders
EXTENSIONS_MUST_LOAD""Comma-separated extension IDs that must load successfully (server fails to start if they don't)

Writing an Extension

Use the CLI to scaffold a new extension:

bash
# Create a bundle (hooks + endpoints)
odp extension:create -n my-extension -t bundle

# Or a single hook / endpoint
odp extension:create -n my-hook -t hook
odp extension:create -n my-endpoint -t endpoint

This generates the project structure, package.json with odp-extension metadata, TypeScript config, and source templates.

A minimal hook extension:

extensions/
  my-hook/
    package.json
    tsconfig.json
    src/
      index.ts

package.json:

json
{
  "name": "my-hook",
  "version": "1.0.0",
  "type": "module",
  "odp-extension": {
    "id": "my-hook",
    "type": "hook"
  },
  "scripts": {
    "build": "tsup src/index.ts --format esm --out-dir dist --no-splitting",
    "dev": "tsup src/index.ts --format esm --out-dir dist --no-splitting --watch src"
  },
  "devDependencies": {
    "tsup": "^8.0.0",
    "typescript": "^5.7.0",
    "@odp/extensions-sdk": "*"
  }
}

src/index.ts:

typescript
import { defineHook } from '@odp/extensions-sdk';

export default defineHook((context, { logger }) => {
  context.action('items.create', async (meta) => {
    (logger as any).info({ collection: meta.collection, key: meta.key }, 'Item created');
  });

  context.filter('items.create', async (payload, meta) => {
    if (meta.collection === 'articles') {
      payload.status = payload.status ?? 'draft';
    }
    return payload;
  });
});

Build and register:

bash
cd extensions/my-hook
npm install
npm run build

The Extension Manager auto-discovers extensions with valid odp-extension metadata. To manually register:

sql
INSERT INTO odp_extensions (id, enabled, folder, source)
VALUES (gen_random_uuid(), true, 'my-hook', 'local');

For the full development guide including event types, endpoint routing, runtime context, and examples, see Extension Development Guide.

ODP Internal API Documentation