Skip to content
Aorux01 edited this page Feb 18, 2026 · 4 revisions

Plugin System

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.

Configuration

The plugin system is controlled via the server.properties file:

plugins=true    # Enable/disable plugin loading

When plugins=true, the server automatically scans the plugins/ directory at startup and loads all valid .js files or subdirectories containing an index.js file.


Architecture

How It Works

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

Loading Mechanism

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.


Plugin Structure

Properties and Methods

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
license string No Plugin license (e.g., "MIT")
repository string No Repository URL
homepage string No Plugin homepage URL
bugs string No Bug tracker URL
minBackendVersion string No Minimum required backend version (e.g., "1.2.0")
dependencies object No Dependencies (npm packages and other plugins)
init(pluginManager) async function Yes Initialization method
shutdown() async function No Cleanup method

Minimal Template

class MyPlugin {
    constructor() {
        // Basic information (required)
        this.name = "MyPlugin";
        this.version = "1.0.0";
        this.description = "My plugin description";
        this.author = "Your Name";

        // Additional metadata (optional)
        this.license = "MIT";
        this.repository = "https://github.com/username/my-plugin";
        this.homepage = "https://github.com/username/my-plugin#readme";
        this.bugs = "https://github.com/username/my-plugin/issues";

        // Version compatibility
        this.minBackendVersion = "1.2.0";  // Plugin won't load on older versions

        // Dependencies (new format)
        this.dependencies = {
            npm: ["axios", "moment"],           // NPM packages to install
            plugins: ["other-plugin-name"]      // Other Neodyme plugins required
        };
    }

    async init(pluginManager) {
        // Initialization code
        // Return true = success, false = failure
        return true;
    }

    async shutdown() {
        // Resource cleanup (optional but recommended)
    }
}

module.exports = MyPlugin;

Legacy Format Support

For backward compatibility, the old format is still supported:

class MyPlugin {
    name = "MyPlugin";
    version = "1.0.0";
    description = "My plugin description";
    author = "Your Name";
    minBackendVersion = "1.2.0";
    dependencies = ["axios", "moment"];  // Old format (treated as npm dependencies)

    async init(pluginManager) {
        return true;
    }
}

module.exports = MyPlugin;

Validation

The PluginManager checks several conditions before loading a plugin:

  1. plugin.name: must be defined and non-empty
  2. plugin.init: must be a function
  3. 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)

Version Compatibility

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

PluginManager API

Available Methods

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

Usage Within a Plugin

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;
}

Available Services

Plugins have access to Neodyme's internal services:

LoggerService

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');

ConfigManager

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');

Discord Integration

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;
}

Lifecycle

Initialization Phase

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

Unloading Phase

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

Hot-Reloading a Plugin

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

Examples

Basic Plugin

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;

Plugin with Periodic Tasks

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;

Plugin with Dependencies

const LoggerService = require('../src/service/logger/logger-service');
const PluginManager = require('../src/manager/plugin-manager');

class DependentPlugin {
    constructor() {
        this.name = "DependentPlugin";
        this.version = "1.0.0";
        this.description = "Plugin that depends on other plugins and npm packages";
        this.author = "Your Name";

        // Dependencies in new format
        this.dependencies = {
            npm: ["axios", "moment"],                    // NPM packages
            plugins: ["DatabasePlugin", "CachePlugin"]   // Other plugins required
        };
    }

    async init(pluginManager) {
        // Check that plugin dependencies are loaded
        const plugins = PluginManager.getPlugins();

        if (this.dependencies.plugins && this.dependencies.plugins.length > 0) {
            for (const dep of this.dependencies.plugins) {
                const found = plugins.find(p => p.name === dep);
                if (!found) {
                    LoggerService.log('error', `Missing plugin dependency: ${dep}`);
                    return false;
                }
            }
        }

        LoggerService.log('success', 'All dependencies found');

        // NPM dependencies are automatically installed by the plugin installer
        // You can now safely use them
        const axios = require('axios');
        const moment = require('moment');

        return true;
    }
}

module.exports = DependentPlugin;

Automatic NPM Dependency Installation

When installing a plugin from the store with /plugins store install, NPM dependencies are automatically installed:

/plugins store install my-plugin
# Output: Installing plugin: MyPlugin v1.0.0
#         ...
#         Installing npm dependencies...
#         Installing npm dependencies: axios, moment
#           added 2 packages in 3s
#         Dependencies installed successfully

Note: For manually installed plugins (not from the store), you must install NPM dependencies yourself:

npm install axios moment

Discord Plugin

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;

Best Practices

Complete Plugin Metadata

For plugins published to the store, always include complete metadata:

constructor() {
    // Required
    this.name = "my-plugin";
    this.version = "1.0.0";

    // Highly recommended for store
    this.description = "Clear, concise description of what your plugin does";
    this.author = "Your Name or Organization";
    this.license = "MIT";  // or GPL-3.0, Apache-2.0, etc.

    // URLs for community
    this.repository = "https://github.com/username/my-plugin";
    this.homepage = "https://github.com/username/my-plugin#readme";
    this.bugs = "https://github.com/username/my-plugin/issues";

    // Compatibility
    this.minBackendVersion = "1.2.0";

    // Dependencies
    this.dependencies = {
        npm: ["axios"],    // List all NPM packages used
        plugins: []        // List required Neodyme plugins
    };
}

Error Handling

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;
    }
}

Resource Cleanup

Always implement shutdown() if your plugin uses:

  • Intervals or timeouts
  • Database connections
  • WebSocket connections
  • Open files
  • Event listeners

Relative Paths

Use path.join and __dirname for file paths:

const path = require('path');

async init(pluginManager) {
    const configPath = path.join(__dirname, 'config.json');
    // ...
}

Appropriate Logging

Use the correct log level:

  • info: general information
  • success: successful operations
  • warn: abnormal but non-blocking situations
  • error: errors requiring attention

Troubleshooting

Plugin Not Loading

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

Plugin Not Reloading

Hot-reload requires:

  1. File must be in the plugins/ directory
  2. Name passed to reloadPlugin() must exactly match the plugin's name
  3. JavaScript file must have the same name as the plugin (without .js extension)

Memory Leaks

If your server consumes increasing memory:

  1. Verify shutdown() cleans up all intervals/timeouts
  2. Verify event listeners are removed
  3. Verify connections are closed

Directory Structure

Plugins can be organized in two ways:

Single File Plugins

neodyme/
  plugins/
    MyPlugin.js             # Single file plugin
    AnotherPlugin.js

Subdirectory Plugins (Recommended for complex plugins)

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 .js files in plugins/
  • Subdirectories containing an index.js file

Full Project Structure

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

Plugin Store

Neodyme features an integrated plugin store that allows you to discover, install, and update plugins directly from the console.

Using the Plugin Store

Browse Available Plugins

/plugins store list        # Show all available plugins
/plugins store search discord   # Search for specific plugins

View Plugin Details

/plugins store info discord-integration
# Shows: description, author, version, dependencies, ratings, etc.

Install a Plugin

/plugins store install discord-integration
# Downloads files, installs NPM dependencies, and loads the plugin automatically

Update Plugins

/plugins store update discord-integration
# Updates to the latest version from the store

Plugin Store Features

  • πŸ“¦ Automatic Installation: Downloads all plugin files and installs NPM dependencies
  • πŸ“Š Progress Tracking: Real-time progress bars during download
  • πŸ” Smart Search: Search by name, author, description, or tags
  • ⚑ Version Check: Ensures backend compatibility before installation
  • πŸ”„ Hot-Reload: Plugins are automatically loaded after installation
  • πŸ“ Metadata: View ratings, download counts, and detailed information

Publishing to the Plugin Store

To publish your plugin to the official Neodyme Plugin Store:

  1. Prepare your plugin with all required properties:

    this.name = "my-plugin";
    this.version = "1.0.0";
    this.description = "Clear description";
    this.author = "Your Name";
    this.license = "MIT";
    this.repository = "https://github.com/username/my-plugin";
    this.dependencies = {
        npm: ["package1", "package2"],
        plugins: []
    };
  2. Test your plugin locally

  3. Create a manifest (see PLUGIN_STORE_SETUP.md)

  4. Submit a pull request to Neodyme-Plugins

Store Repository Structure

Neodyme-Plugins/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ plugins.json                    # List of all plugins
β”‚   └── plugins/
β”‚       └── my-plugin.json              # Plugin manifest
└── plugins/
    └── my-plugin/
        β”œβ”€β”€ index.js                    # Main plugin file
        β”œβ”€β”€ config.json                 # Configuration
        └── README.md                   # Documentation

Community Plugins

See all available plugins

Clone this wiki locally