Enterprise-Grade Modular Plugin System - A production-ready, extensible plugin architecture designed for low-code platforms, featuring dependency management, lifecycle tracking, priority-based execution, and comprehensive error handling.
π Read the detailed blog post: Designing a Modular Plugin Architecture for Low-Code Platforms
This repository demonstrates a production-ready plugin architecture suitable for building extensible low-code platforms, content management systems, and any application requiring a robust plugin ecosystem. The architecture emphasizes:
- π Modular Design: Clean separation of concerns with pluggable components
- π Lifecycle Management: Complete plugin lifecycle from discovery to shutdown
- π Dependency Resolution: Automatic dependency ordering and circular dependency detection
- β‘ Priority-Based Execution: Control plugin execution order via priority system
- π‘οΈ Error Isolation: Plugin failures don't crash the entire system
- π Observability: Built-in metrics, logging, and event history
- π Sandboxed APIs: Secure plugin execution with capability-based access control
- Architecture
- Core Concepts
- Getting Started
- Plugin Development
- API Reference
- Advanced Features
- Best Practices
- Examples
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PluginPlatform β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β EventBus β β Registry β β Loader β β
β β β β β β β β
β β - Priority β β - Lifecycle β β - Discovery β β
β β - Middleware β β - Dependenciesβ β - Validation β β
β β - History β β - Stats β β - Sandboxing β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββ
β Plugin Instances β
β ββββββββββββ ββββββββββββ β
β β Plugin A β β Plugin B β ... β
β ββββββββββββ ββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
- Coordinates plugin discovery, loading, and registration
- Manages platform lifecycle (init, shutdown)
- Provides unified API for event emission and plugin access
- Tracks platform-wide statistics and metrics
- Priority-based handler execution
- Middleware support for cross-cutting concerns
- Error isolation (one handler failure doesn't stop others)
- Event history and debugging capabilities
- Timeout support for long-running handlers
- Stores plugin metadata and instances
- Tracks plugin lifecycle states (registered, active, error, disabled)
- Dependency graph management and validation
- Topological sorting for dependency-ordered execution
- Plugin statistics and health monitoring
- Discovers plugins from directory structure
- Validates manifest schemas and types
- Loads and initializes plugin modules
- Creates sandboxed API for each plugin
- Handles errors gracefully with configurable strategies
Every plugin must declare a manifest.json file that describes its metadata, capabilities, and behavior:
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Plugin description",
"entry": "./index.js",
"hooks": ["beforeSave", "afterSave"],
"priority": 50,
"enabled": true,
"dependencies": ["other-plugin"],
"capabilities": {
"readRecord": true,
"writeRecord": true
},
"config": {
"customSetting": "value"
}
}Required Fields:
name: Unique plugin identifierversion: Semantic version (e.g., "1.0.0")entry: Path to plugin entry point (relative to plugin directory)hooks: Array of hook names this plugin subscribes to
Optional Fields:
priority: Execution priority (lower = higher priority, default: 100)enabled: Enable/disable plugin (default: true)dependencies: Array of plugin names this plugin depends oncapabilities: Object declaring plugin capabilitiesconfig: Plugin-specific configuration objecttimeout: Handler timeout in milliseconds
Plugins export a default function that receives a sandboxed API and returns hook handlers:
export default function myPlugin(api) {
// Access plugin API
api.log('Plugin initializing');
api.warn('Warning message');
api.error('Error message');
// Access capabilities
if (api.capabilities.writeRecord) {
// Safe to write
}
// Access configuration
const setting = api.config.customSetting;
// Return hook handlers
return {
async beforeSave(record) {
// Modify record before save
record.modified = true;
api.log('Record modified');
},
async afterSave(record) {
// Post-save logic
api.log('Record saved:', record.id);
}
};
}Hooks are events that plugins can subscribe to. The platform emits hooks at specific lifecycle points:
- beforeSave: Executed before a record is saved (allows modification)
- afterSave: Executed after a record is saved (read-only)
- platform:shutdown: Executed during platform shutdown (cleanup)
Plugins execute in priority order (lower priority number = executes first).
Plugins can declare dependencies on other plugins:
{
"name": "plugin-b",
"dependencies": ["plugin-a"]
}The platform automatically:
- Validates all dependencies exist
- Detects circular dependencies
- Orders plugin execution based on dependency graph
- Loads dependencies before dependents
- Node.js >= 18.0.0
- npm or yarn
# Clone the repository
git clone https://github.com/nivedhapalani96/lowcode-plugin-architecture-demo.git
cd lowcode-plugin-architecture-demo
# Install dependencies (none required, but keeps workflow familiar)
npm install# Run the main demo
npm run demo
# List all discovered plugins
npm run list:plugins-
Create plugin directory:
mkdir -p src/plugins/my-plugin
-
Create manifest.json:
{ "name": "my-plugin", "version": "1.0.0", "description": "My awesome plugin", "entry": "./index.js", "hooks": ["beforeSave"], "priority": 100 } -
Implement plugin (index.js):
export default function myPlugin(api) { api.log('My plugin loaded'); return { async beforeSave(record) { // Your logic here record.processedBy = 'my-plugin'; } }; }
-
Test your plugin:
npm run demo
The api object provided to plugins includes:
api.log(...args): Log informational messagesapi.warn(...args): Log warningsapi.error(...args): Log errors
api.manifest: Read-only plugin manifestapi.capabilities: Read-only capabilities objectapi.config: Plugin configuration object
api.platform.emit(eventName, payload): Emit custom eventsapi.platform.getPlugin(name): Get another plugin instance
api.utils.validate(value, schema): Validate valuesapi.utils.clone(obj): Deep clone objects
const platform = new PluginPlatform(options);Options:
pluginsDir(string): Custom plugins directory patheventBus(object): EventBus configurationenableHistory(boolean): Enable event historymaxHistorySize(number): Max history entriesdefaultPriority(number): Default handler priority
loader(object): Loader configurationenabledOnly(boolean): Only load enabled pluginsvalidateDependencies(boolean): Validate dependenciesonError(string): Error handling strategy ('warn', 'throw', 'skip')
Initialize the platform and load all plugins.
const result = await platform.init();
// Returns: { initialized, initTime, plugins, loadOrder }Emit an event to all registered handlers.
const result = await platform.emit('beforeSave', record, {
stopOnError: false,
timeout: 5000
});
// Returns: { success, executed, errors, duration }Get all registered plugin manifests.
const plugins = platform.listPlugins();Get a specific plugin by name.
const plugin = platform.getPlugin('my-plugin');Get platform statistics.
const stats = platform.getStats();
// Returns: { initTime, totalEvents, totalErrors, plugins }Add event bus middleware.
platform.use(async (payload, next) => {
console.log('Before handlers');
await next();
console.log('After handlers');
});Shutdown the platform and cleanup.
await platform.shutdown();Control plugin execution order using the priority field:
{
"name": "high-priority-plugin",
"priority": 10, // Executes first (lower number = higher priority)
"hooks": ["beforeSave"]
}Add cross-cutting concerns using middleware:
platform.use(async (payload, next) => {
const start = Date.now();
await next();
console.log(`Event took ${Date.now() - start}ms`);
});Configure how the loader handles plugin errors:
const platform = new PluginPlatform({
loader: {
onError: 'warn' // Options: 'warn', 'throw', 'skip'
}
});Enable event history for debugging:
const platform = new PluginPlatform({
eventBus: {
enableHistory: true,
maxHistorySize: 100
}
});
// Access history
const history = platform.eventBus.getHistory();The platform automatically validates dependencies:
// Circular dependency detection
// Missing dependency detection
// Automatic topological sorting- Single Responsibility: Each plugin should do one thing well
- Idempotency: Hook handlers should be idempotent when possible
- Error Handling: Always handle errors gracefully within plugins
- Logging: Use appropriate log levels (log, warn, error)
- Async Operations: Use async/await for I/O operations
- Timeouts: Set reasonable timeouts for long-running operations
- Priority: Use priority to optimize critical path execution
- Lazy Loading: Defer heavy initialization until needed
- Capability Checks: Always check capabilities before operations
- Input Validation: Validate and sanitize inputs
- Sandboxing: Don't expose sensitive platform internals
- Dependencies: Minimize plugin dependencies
- Unit Tests: Test plugin logic in isolation
- Integration Tests: Test plugin interactions
- Error Scenarios: Test error handling paths
- Performance Tests: Monitor plugin performance
- Clear Descriptions: Write clear plugin descriptions
- Hook Documentation: Document what each hook does
- Configuration: Document configuration options
- Examples: Provide usage examples
// src/plugins/hello-world/index.js
export default function helloWorldPlugin(api) {
return {
async beforeSave(record) {
record.greeting = 'Hello, World!';
api.log('Added greeting to record');
}
};
}// src/plugins/advanced-plugin/manifest.json
{
"name": "advanced-plugin",
"version": "1.0.0",
"description": "Depends on another plugin",
"entry": "./index.js",
"hooks": ["beforeSave"],
"dependencies": ["hello-world"],
"priority": 50
}// src/plugins/configurable-plugin/index.js
export default function configurablePlugin(api) {
const prefix = api.config.prefix || 'PREFIX';
return {
async beforeSave(record) {
record.id = `${prefix}-${record.id}`;
api.log(`Prefixed ID with: ${prefix}`);
}
};
}export default function robustPlugin(api) {
return {
async beforeSave(record) {
try {
// Risky operation
if (!record.data) {
throw new Error('Missing data');
}
// Process record
} catch (error) {
api.error('Failed to process record:', error.message);
// Don't re-throw - allow other plugins to continue
}
}
};
}For production deployments, consider:
- Monitoring: Add metrics collection (Prometheus, DataDog, etc.)
- Logging: Integrate with centralized logging (ELK, Splunk, etc.)
- Security: Implement plugin signing and verification
- Performance: Add caching, rate limiting, and resource quotas
- Testing: Comprehensive test coverage and CI/CD pipelines
- Documentation: API documentation and developer guides
- Versioning: Plugin version management and migration strategies
- Rollback: Ability to disable/rollback problematic plugins
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
MIT License - see LICENSE file for details
This architecture is inspired by:
- WordPress plugin system
- VS Code extension architecture
- Webpack plugin system
- Modern microservices patterns
Built with β€οΈ for extensible platforms