Skip to main content

Plugin System

3DPrintForge supports a plugin system that lets you extend functionality without modifying the source code.

Experimental

The plugin system is under active development. The API may change between versions.

What can plugins do?

  • Add new API endpoints
  • Listen to printer events and react to them
  • Add new frontend panels
  • Integrate with third-party services
  • Extend notification channels

Plugin structure

A plugin is a Node.js module in the plugins/ folder:

plugins/
└── my-plugin/
├── plugin.json # Metadata
├── index.js # Entry point
└── README.md # Documentation (optional)

plugin.json

{
"name": "my-plugin",
"version": "1.0.0",
"description": "Description of the plugin",
"author": "Your name",
"main": "index.js",
"hooks": ["onPrintStart", "onPrintEnd", "onPrinterConnect"]
}

index.js

module.exports = {
// Called when the plugin is loaded
async onLoad(context) {
const { api, db, logger, events } = context;
logger.info('My plugin is loaded');

// Register a new API route
context.registerRoute('GET', '/plugins/my-plugin/status', (req, res) => {
res.json({ status: 'active' });
});

// Register a frontend panel (appears in the sidebar)
context.registerPanel({
id: 'my-plugin-panel',
title: 'My Plugin',
icon: 'fas fa-plug',
html: '<div id="my-plugin-container"></div>',
js: '/plugins/my-plugin/panel.js'
});

// Schedule a recurring task (runs every 5 minutes)
context.setInterval(() => {
logger.info('Running periodic check');
}, 5 * 60 * 1000);
},

// Called when a print starts
async onPrintStart(context, printJob) {
const { logger } = context;
logger.info(`Print started: ${printJob.name}`);
},

// Called when a print is done
async onPrintEnd(context, printJob) {
const { logger, db } = context;
logger.info(`Print done: ${printJob.name}`);
// Save data to the database using db access
db.run(
'INSERT INTO plugin_data (plugin, key, value) VALUES (?, ?, ?)',
['my-plugin', 'last-print', printJob.name]
);
}
};

Available hooks

HookTriggers
onLoadPlugin is loaded
onUnloadPlugin is unloaded
onPrinterConnectPrinter connects
onPrinterDisconnectPrinter disconnects
onPrintStartPrint starts
onPrintEndPrint completes
onPrintFailPrint fails
onFilamentChangeFilament change
onAmsUpdateAMS status updates

Plugin context

All hooks receive a context object:

PropertyTypeDescription
dbSQLite DatabaseSyncDirect access to the SQLite database (synchronous API)
loggerLoggerStructured logging with plugin name prefix
eventsEventEmitterListen to and emit events
configObjectDashboard configuration (read-only)
printersMapAll connected printers with live status

Plugin API methods

The context also provides these methods for extending the dashboard:

MethodDescription
registerRoute(method, path, handler)Register a custom HTTP endpoint (GET, POST, PUT, DELETE)
registerPanel(options)Add a new panel to the dashboard sidebar with custom HTML and JS
setInterval(callback, ms)Schedule a recurring task (automatically cleared on plugin unload)
db.run(sql, params)Execute a write query on the database
db.all(sql, params)Execute a read query and return all rows
db.get(sql, params)Execute a read query and return the first row

registerRoute

context.registerRoute('POST', '/plugins/my-plugin/action', (req, res) => {
const body = req.body;
// Process the request
res.json({ success: true });
});

Routes are automatically prefixed and protected by the authentication system. API key and session auth both work.

registerPanel

context.registerPanel({
id: 'my-panel', // Unique panel identifier
title: 'My Panel', // Sidebar label
icon: 'fas fa-chart-bar', // FontAwesome icon class
html: '<div>...</div>', // HTML content or path to HTML file
js: '/plugins/my-plugin/panel.js' // Optional JS to load
});

setInterval

const interval = context.setInterval(() => {
// Periodic work (e.g. poll external API, clean up old data)
}, 60000);

Intervals registered via context.setInterval are automatically cleared when the plugin is unloaded, preventing memory leaks.

Installing a plugin

# Copy the plugin folder
cp -r my-plugin/ plugins/

# Restart the dashboard
npm start

Plugins are activated automatically at startup if they are found in the plugins/ folder.

Disabling a plugin

Add "disabled": true to plugin.json, or remove the folder.

Example plugin: Slack notifications

const { IncomingWebhook } = require('@slack/webhook');

module.exports = {
async onLoad(context) {
this.webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);
},

async onPrintEnd(context, job) {
await this.webhook.send({
text: `Print done! *${job.name}* took ${job.duration}`
});
}
};