ObjectStackObjectStack

Plugin Anatomy

The Plugin contract — interface, context, and lifecycle phases every ObjectStack plugin implements

Plugin Anatomy

Plugins are the building blocks of ObjectStack. A plugin is a plain JavaScript/TypeScript object (or class) that conforms to the Plugin interface.

Anatomy of a Plugin

import type { Plugin, PluginContext } from '@objectstack/core';
import { z } from 'zod';

export class MyPlugin implements Plugin {
    // Identity
    name = 'com.example.myplugin';
    version = '1.0.0';

    // Dependencies (Optional)
    // An array of *plugin names* the kernel must initialize before this one.
    // This controls init ordering — it is NOT an npm-style version map.
    dependencies = ['com.objectstack.engine.objectql'];

    // Configuration Schema (Optional)
    // Read by the plugin loader to validate config; it lives on the plugin
    // metadata rather than the base `Plugin` interface.
    configSchema = z.object({
        apiKey: z.string()
    });

    /**
     * Init Phase (REQUIRED)
     * Use this to register services, listeners, or other early setup.
     */
    async init(ctx: PluginContext) {
        // Register a service
        ctx.registerService('myService', new MyService());
    }

    /**
     * Start Phase (Optional)
     * All plugins are initialized. Use this to start servers or background jobs.
     */
    async start(ctx: PluginContext) {
        console.log('MyPlugin started!');
    }

    /**
     * Destroy Phase (Optional)
     * Called during kernel shutdown — clean up resources here.
     */
    async destroy() {
        console.log('MyPlugin stopped!');
    }
}

Plugin Types

ObjectStack uses type discrimination to optimize runtime behavior, allowing the kernel to intelligently handle different kinds of extensions.

1. Standard Plugin (standard)

  • Role: General purpose backend logic (default).
  • Use Cases: Registering Services, Hooks, Utilities, or non-UI middleware.
  • Behavior: Executed in the Node.js runtime with full access to the Kernel.

2. UI Plugin (ui)

  • Role: Frontend application host.
  • Use Cases: Admin Console, Low-code Studio, SPA Dashboard.
  • Behavior:
    • Passive: Driven by plugin-hono-server (or other HTTP adapters).
    • Static Assets: Must provide staticPath pointing to a build output (e.g., dist/).
    • Routing: Automatically mounted to /{slug} with SPA fallback support.
    • Assets: Files are served locally under /{slug}/assets.

3. App Plugin (app)

  • Role: Vertical Business Solution.
  • Use Cases: CRM, Project Management, ERP modules.
  • Behavior:
    • Contains rich metadata (Objects, Flows, Reports).
    • May bundle ui-plugin references for custom widgets.
    • Focuses on business logic rather than infrastructure.

4. Driver Plugin (driver)

  • Role: Infrastructure Connectivity.
  • Use Cases: SQL Adaptors (@objectstack/driver-sql), Storage (driver-s3).
  • Behavior: Loaded early in the lifecycle to ensure data availability.

5. Server Plugin (server)

  • Role: Protocol Gateway.
  • Use Cases: REST API (plugin-hono-server), GraphQL, WebSocket.
  • Behavior: Responsible for binding ports and mapping incoming traffic.

6. Theme Plugin (theme)

  • Role: UI Appearance.
  • Use Cases: Dark Mode, Enterprise Branding.
  • Behavior: Provides CSS tokens and asset overrides to ui-plugins.

7. Agent Plugin (agent)

  • Role: AI Capability.
  • Use Cases: RAG Pipelines, Autonomous Agents.
  • Behavior: Extensions for the AI Gateway.

Plugin Interface Reference

The Plugin interface (exported from @objectstack/core) defines the contract that all functionality modules must implement.

export interface Plugin {
    /**
     * Plugin Unique Identifier
     * Recommended format: com.organization.plugin-name
     */
    name: string;

    /**
     * Plugin Version (Optional)
     */
    version?: string;

    /**
     * Plugin Type (Optional)
     * One of: standard, ui, driver, server, app, theme, agent.
     * @default 'standard'
     */
    type?: string;

    /**
     * Dependencies (Optional)
     * List of other plugin names that this plugin depends on.
     * The kernel ensures these plugins are initialized before this one.
     */
    dependencies?: string[];

    /**
     * Init Phase (REQUIRED)
     * Called when the kernel is initializing. Use this to:
     * - Register Services
     * - Register Event Listeners
     * - Extend Metadata
     */
    init(ctx: PluginContext): Promise<void> | void;

    /**
     * Start Phase (Optional)
     * Called after all plugins are initialized. Use this to:
     * - Start HTTP servers
     * - Connect to databases
     * - Start background workers
     */
    start?(ctx: PluginContext): Promise<void> | void;

    /**
     * Destroy Phase (Optional)
     * Called during kernel shutdown. Clean up resources here.
     */
    destroy?(): Promise<void> | void;
}

Plugin Context

The PluginContext provides access to kernel capabilities:

interface PluginContext {
  /** Register a service for other plugins to consume */
  registerService(name: string, service: any): void;

  /** Register a lifecycle-managed service factory (e.g. SCOPED per-project services) */
  registerServiceFactory(
    name: string,
    factory: (ctx: PluginContext, scopeId?: string) => any,
    lifecycle?: ServiceLifecycle,
    dependencies?: string[],
  ): void;

  /** Get a service registered by another plugin */
  getService<T>(name: string): T;

  /** Get a per-scope service instance (e.g. per-project / per-environment) */
  getServiceScoped<T>(name: string, scopeId: string): Promise<T>;

  /** Replace an existing service implementation */
  replaceService<T>(name: string, implementation: T): void;

  /** Get all registered services */
  getServices(): Map<string, any>;

  /** Register a hook handler */
  hook(name: string, handler: (...args: any[]) => void | Promise<void>): void;

  /** Trigger a hook */
  trigger(name: string, ...args: any[]): Promise<void>;

  /** Logger instance */
  logger: Logger;

  /** Access the kernel instance */
  getKernel(): ObjectKernel;
}

Plugin Lifecycle

Plugins follow a strict three-phase lifecycle managed by the kernel:

                ┌──────────────────────┐
                │    kernel.use()      │
                │   Register plugin    │
                └──────────┬───────────┘

                ┌──────────▼───────────┐
                │   Phase 1: init()    │
                │  Register services   │
                │  Set up hooks        │
                └──────────┬───────────┘

                ┌──────────▼───────────┐
                │   Phase 2: start()   │
                │  Start servers       │
                │  Connect databases   │
                │  Execute logic       │
                └──────────┬───────────┘

                ┌──────────▼───────────┐
                │ kernel:ready hook    │
                │  System operational  │
                └──────────┬───────────┘

                ┌──────────▼───────────┐
                │  Phase 3: destroy()  │
                │  Cleanup resources   │
                │  (reverse order)     │
                └──────────────────────┘
  1. init() — Called during kernel initialization. Register services that other plugins may depend on.
  2. start() — Called after all plugins have initialized. Start servers, connect to databases, or execute main logic.
  3. destroy() — Called during shutdown, in reverse order. Clean up connections, timers, and resources.

Next Steps

  • Plugin System — Module overview: architecture, configuration, and built-in plugins
  • Plugin Development — Step-by-step tutorial: build, test, and register a plugin

On this page