Plugins
Learn how to create and use plugins to extend Svelar's functionality.
What are Plugins?
Plugins are modular, self-contained packages that extend Svelar. They can register services, add middleware, create CLI commands, define configuration, provide routes, and listen to events.
Plugin Lifecycle
Plugins go through these lifecycle stages:
1. register(app) - Register dependencies and configuration
↓
2. boot(app) - Resolve dependencies and initialize
↓
3. shutdown() - Clean up resources
All plugins are registered first, then all are booted — so during boot() you can safely access services registered by other plugins.
Creating a Plugin
npx svelar make:plugin AnalyticsPlugin
This creates src/lib/shared/plugins/AnalyticsPlugin.ts:
import { Plugin } from '@beeblock/svelar/plugins';
import type { Container } from '@beeblock/svelar/container';
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
description = 'Analytics tracking for Svelar apps';
async register(app: Container): Promise<void> {
// Register services, bindings, etc.
app.singleton('analytics', () => new AnalyticsService());
}
async boot(app: Container): Promise<void> {
// Initialize after all plugins registered
console.log('[AnalyticsPlugin] Booted successfully');
}
async shutdown(): Promise<void> {
// Clean up resources (no arguments)
console.log('[AnalyticsPlugin] Shutting down');
}
}
Plugin Methods
register(app)
Register bindings in the service container. Called before any plugin is booted:
async register(app: Container): Promise<void> {
// Register singleton service
app.singleton('analytics', () => new AnalyticsService());
}
boot(app)
Initialize after all plugins are registered. Safe to resolve services from other plugins:
async boot(app: Container): Promise<void> {
const analytics = app.make('analytics');
const config = app.make('config').get('analytics');
if (config.enabled) {
analytics.init();
}
}
shutdown()
Clean up resources when the application shuts down. Takes no arguments:
async shutdown(): Promise<void> {
// Flush pending data, close connections, etc.
}
Plugin Capabilities
Configuration
Return a PluginConfig from the config() method to register default configuration:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
config() {
return {
key: 'analytics',
defaults: {
enabled: true,
trackPageViews: true,
trackQueryParams: false,
},
};
}
}
Users override the defaults by creating a config file:
// config/analytics.ts
export default {
enabled: true,
trackPageViews: true,
trackErrors: false,
samplingRate: 0.1,
};
Middleware
Register middleware that runs on every request:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
middleware() {
return [
{
name: 'track-views',
handler: async (ctx: any, next: any) => {
// Track page view
console.log('Page visited:', ctx.event.url.pathname);
return next();
},
},
];
}
}
Routes
Register custom API routes. Every route requires a method, path, and handler:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
routes() {
return [
{
method: 'GET' as const,
path: '/api/analytics/stats',
handler: async (event: any) => {
return new Response(JSON.stringify({ views: 42 }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
},
},
];
}
}
Migrations
Return paths to database migration files the plugin provides:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
migrations() {
return [
'./database/migrations/create_events_table.ts',
'./database/migrations/create_page_views_table.ts',
];
}
}
Commands
Register CLI commands. Each command is a PluginCommand object with name, description, and handler:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
commands() {
return [
{
name: 'analytics:clear',
description: 'Clear all tracked analytics events',
handler: async (args: string[]) => {
console.log('Clearing analytics...');
// Clear stored events
},
},
{
name: 'analytics:report',
description: 'Generate analytics report',
handler: async (args: string[]) => {
const days = parseInt(args[0] ?? '7');
console.log(`Generating ${days}-day report...`);
},
},
];
}
}
Listeners
Register event listeners. Each listener has an event name and a handler function:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
listeners() {
return [
{
event: 'user:registered',
handler: async (user: any) => {
console.log(`[Analytics] User signed up: ${user.id}`);
},
},
{
event: 'order:completed',
handler: async (order: any) => {
console.log(`[Analytics] Order completed: ${order.id}`);
},
},
];
}
}
Publishable Assets
Declare files that can be published to the user's app via npx svelar plugin:publish:
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
publishables() {
return {
analytics: [
{ source: './config/analytics.ts', dest: 'config/analytics.ts', type: 'config' as const },
{ source: './migrations/create_events.ts', dest: 'src/lib/database/migrations/create_events.ts', type: 'migration' as const },
],
};
}
}
Complete Plugin Example
// src/lib/shared/plugins/AnalyticsPlugin.ts
import { Plugin } from '@beeblock/svelar/plugins';
import type { Container } from '@beeblock/svelar/container';
export class AnalyticsPlugin extends Plugin {
readonly name = 'svelar-analytics';
readonly version = '1.0.0';
description = 'Simple analytics tracking for page views';
private pageViews = new Map<string, number>();
async register(app: Container): Promise<void> {
app.instance('analytics', this);
}
async boot(app: Container): Promise<void> {
console.log('[AnalyticsPlugin] Booted successfully');
}
track(path: string): void {
const current = this.pageViews.get(path) ?? 0;
this.pageViews.set(path, current + 1);
}
getStats(): Record<string, number> {
return Object.fromEntries(this.pageViews);
}
config() {
return {
key: 'analytics',
defaults: {
enabled: true,
trackQueryParams: false,
},
};
}
middleware() {
return [
{
name: 'analytics',
handler: async (ctx: any, next: any) => {
this.track(ctx.event.url.pathname);
return next();
},
},
];
}
routes() {
return [
{
method: 'GET' as const,
path: '/api/analytics/stats',
handler: async () => {
return new Response(JSON.stringify(this.getStats()), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
},
},
];
}
commands() {
return [
{
name: 'analytics:clear',
description: 'Clear tracked page views',
handler: async () => {
this.pageViews.clear();
console.log('Analytics cleared.');
},
},
];
}
}
Using Plugins
Registering Plugins
Register plugins in your application bootstrap. PluginManager requires a Container instance:
import { container } from '@beeblock/svelar/container';
import { PluginManager } from '@beeblock/svelar/plugins';
import { AnalyticsPlugin } from '$lib/shared/plugins/AnalyticsPlugin.js';
import { SeoPlugin } from '$lib/shared/plugins/SeoPlugin.js';
const pluginManager = new PluginManager(container);
pluginManager.use(new AnalyticsPlugin());
pluginManager.use(new SeoPlugin());
await pluginManager.boot();
You can also register multiple plugins at once:
pluginManager.useMany([
new AnalyticsPlugin(),
new SeoPlugin(),
]);
Auto-Discovery
Discover plugins from a directory using the standalone discoverPlugins function:
import { container } from '@beeblock/svelar/container';
import { discoverPlugins, PluginManager } from '@beeblock/svelar/plugins';
const plugins = await discoverPlugins('./src/lib/shared/plugins');
const pluginManager = new PluginManager(container);
pluginManager.useMany(plugins);
await pluginManager.boot();
Accessing Plugin Services
Access services registered by plugins through the container:
import { container } from '@beeblock/svelar/container';
const analytics = container.make('analytics');
const stats = analytics.getStats();
Plugin Hooks
The PluginManager provides a hook system for cross-plugin communication:
// Register a hook listener
pluginManager.on('app:boot', async () => {
console.log('All plugins booted');
});
// Custom hooks
pluginManager.on('analytics:reset', async () => {
console.log('Analytics data was reset');
});
// Trigger a hook
await pluginManager.triggerHook('analytics:reset');
Built-in hooks: app:boot, app:shutdown, request:before, request:after, model:creating, model:created, model:updating, model:updated, model:deleting, model:deleted.
Plugin Dependencies
Plugins can declare dependencies on other plugins. The PluginManager resolves load order automatically via topological sort and detects circular dependencies:
export class ExtendedAnalyticsPlugin extends Plugin {
readonly name = 'extended-analytics';
readonly version = '1.0.0';
readonly dependencies = ['svelar-analytics'];
async boot(app: Container): Promise<void> {
// Safe — svelar-analytics is guaranteed to be registered and booted first
const analytics = app.make('analytics');
console.log('Extending analytics with advanced tracking');
}
}
CLI Commands
List Plugins
npx svelar plugin:list
Discovers installed plugins (packages matching svelar-* or @scope/svelar-*, or with svelar-plugin keyword in package.json) and shows their status.
Install a Plugin
npx svelar plugin:install @beeblock/svelar-tags
Runs npm install, discovers the plugin, registers it, and publishes migrations/routes. Use --no-publish to skip asset publishing.
Publish Plugin Assets
npx svelar plugin:publish @beeblock/svelar-tags
npx svelar plugin:publish @beeblock/svelar-tags --only migrations
npx svelar plugin:publish @beeblock/svelar-tags --force
Copies plugin's publishable files (config, migrations, route stubs) to your app. Use --force to overwrite existing files.
Official Plugins
All official plugins support plugin:install and plugin:publish:
| Plugin | Migrations | Routes | Tables |
|---|---|---|---|
@beeblock/svelar-tags |
Yes | Yes | tags, taggables |
@beeblock/svelar-comments |
Yes | Yes | comments, comment_reactions |
@beeblock/svelar-settings |
Yes | Yes | settings |
@beeblock/svelar-media |
Yes | Yes | media |
@beeblock/svelar-backup |
Yes | Yes | backups |
@beeblock/svelar-activity-log |
Yes | No | activity_log |
@beeblock/svelar-impersonate |
No | Yes | — |
@beeblock/svelar-datatable |
No | Yes | — |
@beeblock/svelar-charts |
No | Yes | — |
@beeblock/svelar-sitemap |
No | Yes | — |
@beeblock/svelar-social-auth |
No | Yes | — |
@beeblock/svelar-stripe |
Yes | Yes | stripe |
@beeblock/svelar-two-factor |
No | Yes | — |
Creating a Reusable Plugin Package
Package a plugin for publishing to npm:
svelar-analytics/
├── src/
│ ├── index.ts # Export the Plugin class
│ ├── AnalyticsService.ts
│ └── middleware/
│ └── TrackingMiddleware.ts
├── config/
│ └── analytics.ts # Default config (publishable)
├── package.json
└── README.md
{
"name": "svelar-analytics",
"version": "1.0.0",
"keywords": ["svelar-plugin"],
"main": "dist/index.js",
"exports": {
".": "./dist/index.js"
},
"peerDependencies": {
"@beeblock/svelar": ">=0.4.0"
}
}
The svelar-plugin keyword in package.json allows npx svelar plugin:list to auto-discover your plugin when installed.
Users install and register:
npx svelar plugin:install svelar-analytics
import { AnalyticsPlugin } from 'svelar-analytics';
pluginManager.use(new AnalyticsPlugin());
Best Practices
- One responsibility per plugin — plugins should do one thing well
- Provide sensible defaults —
config()should return working defaults - Use dependency declarations — declare
dependenciesinstead of hoping plugins load in order - Clean up in shutdown() — flush buffers, close connections, release resources
- Use clear naming — prefix with
svelar-for published packages - Add
svelar-pluginkeyword — enables auto-discovery viaplugin:list - Version your plugins — use semantic versioning
- Handle missing dependencies — check before accessing optional services
Next Steps
- Learn about Middleware to extend request handling
- Explore Events & Listeners for event-driven architecture
- Check Architecture & Modules for design patterns
- See HTTP & Integrations for creating custom service integrations
Svelar Plugins Guide © 2026