-
Notifications
You must be signed in to change notification settings - Fork 3
Plugin
Neodyme features a plugin system that allows you to extend server functionality without modifying the core codebase. Plugins are JavaScript modules loaded dynamically at server startup.
The plugin system is controlled via the server.properties file:
plugins=true # Enable/disable plugin loadingWhen plugins=true, the server automatically scans the plugins/ directory at startup and loads all valid .js files or subdirectories containing an index.js file.
The PluginManager is a static class that manages the complete plugin lifecycle:
Server startup
|
v
PluginManager.load()
|
v
Scan plugins/ directory
|
v
For each .js file:
1. Clear require cache (hot-reload)
2. Import plugin class
3. Instantiate (new PluginClass())
4. Validate (name + init required)
5. Call init(pluginManager)
6. If success: add to active plugins list
|
v
Plugins active and ready
The PluginManager uses require.cache to enable hot-reload:
// Cache is cleared before each load
delete require.cache[require.resolve(pluginPath)];
const PluginClass = require(pluginPath);
const plugin = new PluginClass();This means you can modify a plugin and reload it without restarting the server.
| Element | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique plugin identifier |
version |
string | No | Plugin version (default: "1.0.0") |
description |
string | No | Plugin description |
author |
string | No | Plugin author name |
minBackendVersion |
string | No | Minimum required backend version (e.g., "1.2.0") |
dependencies |
array | No | List of required plugins |
init(pluginManager) |
async function | Yes | Initialization method |
shutdown() |
async function | No | Cleanup method |
class MyPlugin {
name = "MyPlugin";
version = "1.0.0";
description = "My plugin description";
author = "Your Name";
minBackendVersion = "1.2.0"; // Plugin won't load on older versions
async init(pluginManager) {
// Initialization code
// Return true = success, false = failure
return true;
}
async shutdown() {
// Resource cleanup (optional but recommended)
}
}
module.exports = MyPlugin;The PluginManager checks several conditions before loading a plugin:
-
plugin.name: must be defined and non-empty -
plugin.init: must be a function -
plugin.minBackendVersion: if defined, the current backend version must be >= this value
If conditions 1 or 2 are not met, the plugin is rejected with the error "Invalid plugin structure".
If condition 3 is not met, the plugin is rejected with an error like:
Plugin MyPlugin requires backend version 1.3.0 or higher (current: 1.2.0)
The minBackendVersion property allows you to ensure your plugin only loads on compatible backend versions:
class MyPlugin {
name = "MyPlugin";
minBackendVersion = "1.2.0"; // Won't load on 1.1.x or earlier
// ...
}Version comparison follows semantic versioning (semver):
-
1.2.0>1.1.6 -
1.2.1>1.2.0 -
2.0.0>1.9.9
| Method | Description | Return |
|---|---|---|
PluginManager.load() |
Load all plugins from plugins/ directory |
void |
PluginManager.loadPlugin(name) |
Load a specific plugin by filename or folder name | boolean |
PluginManager.reloadPlugin(name) |
Reload a plugin (shutdown + load) | boolean |
PluginManager.reload() |
Reload all plugins | void |
PluginManager.unload() |
Unload all plugins | void |
PluginManager.unloadAll() |
Alias for unload() | void |
PluginManager.getPlugins() |
Return list of active plugins | array |
PluginManager.getPlugin(name) |
Get a specific plugin by name | Plugin or null |
PluginManager.getPluginInfo(name) |
Get detailed info about a plugin | object or null |
PluginManager.getBackendVersion() |
Get the current backend version | string |
async init(pluginManager) {
// Store reference for later use
this.pluginManager = pluginManager;
// Access other loaded plugins
const plugins = PluginManager.getPlugins();
const otherPlugin = plugins.find(p => p.name === "OtherPlugin");
if (otherPlugin) {
// Interact with the other plugin
otherPlugin.someMethod();
}
return true;
}Plugins have access to Neodyme's internal services:
const LoggerService = require('../src/service/logger/logger-service');
LoggerService.log('info', 'Informational message');
LoggerService.log('success', 'Operation successful');
LoggerService.log('warn', 'Warning message');
LoggerService.log('error', 'Error detected');const ConfigManager = require('../src/manager/config-manager');
// Read a value from server.properties
const debug = ConfigManager.get('debug');
const port = ConfigManager.get('port');
const discordBot = ConfigManager.get('discordBot');If discordBot=true or discordWebhook=true in the configuration, the PluginManager automatically logs that the plugin supports Discord:
async init(pluginManager) {
if (ConfigManager.get('discordBot')) {
// Initialize Discord bot
}
if (ConfigManager.get('discordWebhook')) {
// Initialize webhooks
}
return true;
}1. Server starts
2. PluginManager.load() is called
3. plugins/ directory is scanned
4. Each .js file is processed:
- Module is imported
- Instance is created
- init() is called with pluginManager as parameter
- If init() returns true: plugin is added to the list
- If init() returns false: plugin is ignored
5. Total loaded plugins count is displayed
1. Server stops OR reload is requested
2. PluginManager.unload() is called
3. For each active plugin:
- If shutdown() exists: it is called
- Plugin is removed from the list
4. Plugin list is cleared
1. reloadPlugin("MyPlugin") is called
2. Plugin is found in the list
3. shutdown() is called (if defined)
4. Plugin is removed from the list
5. loadPlugin("MyPlugin") is called
6. require cache is cleared
7. Module is re-imported
8. init() is called
const LoggerService = require('../src/service/logger/logger-service');
class HelloPlugin {
name = "HelloPlugin";
version = "1.0.0";
description = "Demo plugin";
author = "Your Name";
minBackendVersion = "1.2.0";
async init(pluginManager) {
LoggerService.log('success', 'HelloPlugin initialized');
return true;
}
async shutdown() {
LoggerService.log('info', 'HelloPlugin stopped');
}
}
module.exports = HelloPlugin;const LoggerService = require('../src/service/logger/logger-service');
class SchedulerPlugin {
name = "SchedulerPlugin";
version = "1.0.0";
constructor() {
this.intervals = [];
}
async init(pluginManager) {
// Task executed every 5 minutes
const interval = setInterval(() => {
this.periodicTask();
}, 5 * 60 * 1000);
this.intervals.push(interval);
LoggerService.log('success', 'SchedulerPlugin started');
return true;
}
periodicTask() {
LoggerService.log('info', 'Periodic task executed');
}
async shutdown() {
// Important: clean up intervals
this.intervals.forEach(interval => clearInterval(interval));
this.intervals = [];
LoggerService.log('info', 'SchedulerPlugin stopped, intervals cleaned up');
}
}
module.exports = SchedulerPlugin;const LoggerService = require('../src/service/logger/logger-service');
const PluginManager = require('../src/manager/plugin-manager');
class DependentPlugin {
name = "DependentPlugin";
version = "1.0.0";
dependencies = ["DatabasePlugin", "CachePlugin"];
async init(pluginManager) {
// Check that dependencies are loaded
const plugins = PluginManager.getPlugins();
for (const dep of this.dependencies) {
const found = plugins.find(p => p.name === dep);
if (!found) {
LoggerService.log('error', `Missing dependency: ${dep}`);
return false;
}
}
LoggerService.log('success', 'All dependencies found');
return true;
}
}
module.exports = DependentPlugin;const LoggerService = require('../src/service/logger/logger-service');
const ConfigManager = require('../src/manager/config-manager');
class DiscordPlugin {
name = "DiscordPlugin";
version = "2.0.0";
description = "Discord integration for Neodyme";
author = "Neodyme Team";
minBackendVersion = "1.2.0";
async init(pluginManager) {
this.botEnabled = ConfigManager.get('discordBot');
this.webhookEnabled = ConfigManager.get('discordWebhook');
if (this.botEnabled) {
await this.initBot();
}
if (this.webhookEnabled) {
await this.initWebhook();
}
if (!this.botEnabled && !this.webhookEnabled) {
LoggerService.log('warn', 'DiscordPlugin: no integration enabled');
}
return true;
}
async initBot() {
// Bot initialization logic
LoggerService.log('info', 'Discord bot initialized');
}
async initWebhook() {
// Webhook initialization logic
LoggerService.log('info', 'Discord webhooks initialized');
}
async shutdown() {
// Clean disconnect
LoggerService.log('info', 'DiscordPlugin disconnected');
}
}
module.exports = DiscordPlugin;Always wrap initialization code in a try-catch:
async init(pluginManager) {
try {
// Initialization code
await this.connectToDatabase();
await this.loadConfig();
return true;
} catch (error) {
LoggerService.log('error', `${this.name}: ${error.message}`);
return false;
}
}Always implement shutdown() if your plugin uses:
- Intervals or timeouts
- Database connections
- WebSocket connections
- Open files
- Event listeners
Use path.join and __dirname for file paths:
const path = require('path');
async init(pluginManager) {
const configPath = path.join(__dirname, 'config.json');
// ...
}Use the correct log level:
-
info: general information -
success: successful operations -
warn: abnormal but non-blocking situations -
error: errors requiring attention
| Symptom | Probable Cause | Solution |
|---|---|---|
| "Invalid plugin structure" |
name or init missing |
Verify required properties are defined |
| Syntax error | Invalid JavaScript code | Check file syntax |
| "Plugin file does not exist" | Wrong filename | Verify file is in plugins/ or plugins/<name>/index.js
|
| init() returns false | Initialization failure | Check logs for details |
| "requires backend version X.X.X" | Backend too old | Update backend or lower minBackendVersion
|
Hot-reload requires:
- File must be in the
plugins/directory - Name passed to
reloadPlugin()must exactly match the plugin'sname - JavaScript file must have the same name as the plugin (without .js extension)
If your server consumes increasing memory:
- Verify
shutdown()cleans up all intervals/timeouts - Verify event listeners are removed
- Verify connections are closed
Plugins can be organized in two ways:
neodyme/
plugins/
MyPlugin.js # Single file plugin
AnotherPlugin.js
neodyme/
plugins/
my-plugin/ # Plugin folder
index.js # Main plugin file (required)
config.json # Plugin configuration
utils.js # Additional files
discord-integration/
index.js
config.json
The PluginManager automatically detects both formats:
- Direct
.jsfiles inplugins/ - Subdirectories containing an
index.jsfile
neodyme/
plugins/ # Plugins directory
MyPlugin.js # Single file plugin
discord-integration/ # Subdirectory plugin
index.js
config.json
src/
manager/
plugin-manager.js # Plugin manager
service/
logger/
logger-service.js # Logging service
See all available plugins