Skip to main content
This document explains how to create and integrate plugins into DeployStack. The plugin system enables extending DeployStack with additional functionality, cloud providers, database tables, APIs, and UI components.

Overview

DeployStack’s plugin architecture allows for extensible, modular development with built-in security and isolation. Plugins can:
  • Add new database tables and schemas
  • Register new API routes (automatically namespaced for security)
  • Extend core functionality
  • Add support for additional cloud providers
  • Implement custom business logic
  • Define global settings and configuration groups

Security Features

Route Isolation & Security

DeployStack implements strict route isolation to ensure plugins cannot interfere with core functionality or each other:
  • Automatic Namespacing: All plugin routes are automatically prefixed with /api/plugin/<plugin-id>/
  • No Direct Route Access: Plugins cannot register routes directly on the global Fastify instance
  • Sandboxed Registration: Plugins use PluginRouteManager which enforces namespacing
  • Core Route Protection: Plugins cannot access or modify core routes (/api/auth/*, /api/users/*, etc.)

Security Benefits

  1. Prevents Route Hijacking: Malicious plugins cannot override authentication or user management routes
  2. Eliminates Route Conflicts: Multiple plugins cannot register conflicting routes
  3. Predictable API Surface: All plugin APIs follow consistent /api/plugin/<name>/ structure
  4. Easy Auditing: Route ownership is immediately clear from the URL structure
  5. Fail-Safe Design: Plugins that don’t follow the new system simply won’t have routes registered

Example Security Enforcement

Plugin Structure

A basic plugin consists of the following files:

Required Files

  1. package.json - Defines plugin metadata and dependencies
  2. index.ts - Implements the Plugin interface and exports the plugin class
  3. routes.ts - Contains all API route definitions (automatically namespaced)
  4. schema.ts - (Optional) Contains database schema extensions

File Responsibilities

  • index.ts: Plugin metadata, database extensions, global settings, non-route initialization
  • routes.ts: All API route definitions using the isolated PluginRouteManager
  • schema.ts: Database table definitions and schema extensions
  • package.json: Plugin metadata and dependency declarations

Creating a New Plugin

1. Create Plugin Directory

Create a directory for your plugin:

2. Create package.json

Add basic plugin information:

3. Define Database Schema (Optional)

If your plugin requires database tables, create a schema.ts file using PostgreSQL table definitions:

4. Create Routes File

Create a routes.ts file for your API routes:

5. Implement the Plugin Interface

Create an index.ts file that implements the Plugin interface:

Plugin Integration Points

Database Extension

The databaseExtension property allows your plugin to:
  1. Define tables dynamically: Tables are created at runtime from your tableDefinitions
  2. Initialize data: Seed data or perform setup through onDatabaseInit
  3. Maintain security boundaries: Plugin tables are isolated from core migrations

How Plugin Database Tables Work

Security Architecture:
  • Stage 1 (Trusted): Core migrations run first (static, secure)
  • Stage 2 (Untrusted): Plugin tables created dynamically (sandboxed)
  • Clear Separation: Plugin tables cannot interfere with core database structure
Dynamic Table Creation:
  • Plugin tables are NOT included in core migration files
  • Tables are created at runtime from your tableDefinitions
  • System automatically generates CREATE TABLE SQL from your definitions
  • Tables are dropped and recreated during development for clean structure
Table Definition Format:
Important Notes:
  • Use created_at (snake_case) for database column names, not createdAt (camelCase)
  • Timestamp columns with { mode: 'timestamp' } automatically get TIMESTAMP WITH TIME ZONE DEFAULT NOW()
  • Column types are auto-detected and converted for PostgreSQL:
    • id/count → INTEGER
    • *_at/*date → TIMESTAMP WITH TIME ZONE
    • Others → TEXT
  • Tables are prefixed with your plugin ID: my-plugin_my_entities

API Routes

Register API routes using the isolated PluginRouteManager:

Event Listeners

Plugins can listen to system events and react to core application changes:
For complete event documentation, see the Global Event Bus guide.

Access to Core Services

Plugins receive access to:
  • Database instance (db) - For database operations with your plugin tables
  • Route Manager (routeManager) - For registering isolated, namespaced routes
  • Logger (logger) - For structured logging with plugin context
  • Schema Access - Access to the generated database schema including your tables
  • Global Settings - Plugins can define and access their own global settings
  • Event System - Listen to and react to core application events

Plugin Lifecycle

Plugins follow this lifecycle:
  1. Discovery - Plugin is discovered and loaded from the plugins directory
  2. Registration - Plugin table definitions are registered with the schema system
  3. Core Database Setup - Core migrations are applied (trusted, static)
  4. Plugin Table Creation - Plugin tables are created dynamically from definitions
  5. Database Initialization - onDatabaseInit is called for data seeding/setup
  6. Plugin Initialization - initialize method is called for non-route setup
  7. Route Registration - registerRoutes is called to register API endpoints
  8. Runtime - Plugin operates as part of the application
  9. Shutdown - shutdown method is called during application termination

Database Lifecycle Details

The database initialization follows a strict security-first approach:
This ensures that untrusted plugin code cannot interfere with the core database structure while still providing full database functionality.

Testing Your Plugin

To test your plugin:
  1. Place it in the plugins directory
  2. Start the DeployStack server
  3. Check server logs for initialization messages
  4. Test your plugin’s API endpoints

Advanced Plugin Features

Configuration

Your plugin can access configuration provided by the plugin manager:

Plugin Manager APIs

Plugins can access other plugins through the plugin manager:

Frontend Integration

If your plugin needs to extend the UI, you can:
  1. Register API endpoints that provide UI configuration
  2. Use the Plugin Manager to register UI components
  3. Follow frontend plugin documentation for UI extensions

Best Practices

  1. Unique IDs - Ensure your plugin ID is unique and descriptive
  2. Error Handling - Properly handle errors in your plugin
  3. Database Relationships - Be careful with cross-plugin table relationships
  4. Schema Design - Follow naming conventions for your plugin’s tables
  5. Documentation - Include a README.md with your plugin
  6. Versioning - Use semantic versioning for your plugin

Troubleshooting

Plugin Not Loading

  • Check plugin directory structure
  • Ensure your plugin class is exported as default
  • Verify package.json contains required fields

Database Errors

  • Check your schema definitions
  • Ensure proper initialization in onDatabaseInit
  • Verify SQL queries in your plugin

Integration Issues

  • Look for errors during plugin initialization
  • Check console logs for error messages
  • Verify API routes are registered correctly

Example Plugins

See the plugins/example-plugin directory for a working example.

Plugin API Reference

The complete Plugin interface is defined in src/plugin-system/types.ts.

Defining Global Settings via Plugins

Plugins can contribute their own global settings to the DeployStack system. These settings will be managed alongside core global settings and will be editable by users with the global_admin role.

How it Works

  1. Define globalSettingsExtension: In your plugin class, add an optional property globalSettingsExtension.
  2. Structure: This property should be an object implementing the GlobalSettingsExtension interface (defined in src/plugin-system/types.ts). It can contain:
  • groups: An optional array of GlobalSettingGroupForPlugin objects to define new setting groups.
  • settings: A mandatory array of GlobalSettingDefinitionForPlugin objects to define individual settings.
  1. Initialization: During server startup, the PluginManager will:
  • Collect all group and setting definitions from active plugins.
  • Create any new groups defined by plugins if they don’t already exist. If a group ID already exists, the plugin’s group definition is ignored for that specific group, and the existing group is used.
  • Initialize the plugin’s global settings with their default values, but only if a setting with the same key doesn’t already exist (either from core settings or another plugin). Core settings always take precedence.
  1. Access Control: All plugin-defined global settings are subject to the same access control as core settings (i.e., manageable by global_admin).
  2. Security:
  • Core Precedence: Core global settings (defined in services/backend/src/global-settings/) cannot be overridden by plugins.
  • Duplicate Keys: If a plugin attempts to register a setting with a key that already exists (from core or another plugin), the plugin’s setting will be ignored, and a warning will be logged.

Example: Defining Global Settings in a Plugin

Important Considerations

  • Key Uniqueness: Ensure your setting keys are unique, preferably prefixed with your plugin ID (e.g., yourPluginId.category.settingName) to avoid conflicts.
  • Group IDs: If defining new groups, ensure their IDs are unique.
  • Default Values: Provide sensible default values.
  • Encryption: Mark sensitive settings (API keys, passwords) with encrypted: true.
  • Documentation: Document any global settings your plugin introduces in its own README or documentation.

For additional questions or support, please contact the DeployStack team or open an issue on GitHub.