Introduction to the plugin system for developers.

Plugin SDK — Overview

This section is for developers. If you're not a programmer, you can skip this — everything in the previous sections works without writing any code.


The plugin system lets developers extend ED5 with custom game mechanics, chat commands, server events, HUD widgets, and client-side features. There are two types:

  • Server plugins — run on the game server with access to all game systems
  • Client plugins — run in the player's browser with HUD widget and chat APIs

Where Plugins Live

The Studio app manages plugins through the Plugins editor in the sidebar:

Plugin Manager — Code editor with plugin list, server/client tabs, and live editing
Plugin Manager — Code editor with plugin list, server/client tabs, and live editing
① Plugin list with enable/disable toggles② Built-in code editor with syntax highlighting③ Server and Client tabs for creating new plugins
You can also create plugin files directly on disk:
TypeLocationExtension
Server pluginsplugins/ folder inside the Studio data directory.ts
Client pluginsclient-plugins/ folder inside the Studio data directory.js

Auto-Loading

Plugins load automatically when the server starts. Just create a file (or use the built-in editor) and restart.

Naming Rules

PatternBehavior
my-plugin.tsServer plugin — loaded automatically
my-widget.jsClient plugin — sent to all connected players
_disabled-plugin.tsSkipped (underscore prefix disables)

Your First Server Plugin

Create a new server plugin in the Plugin editor (or as a .ts file):

import type { PluginDefinition } from "../src/core/plugin-system.js";

const plugin: PluginDefinition = { name: "welcome", version: "1.0.0", description: "Welcomes players on login",

register(ctx) { ctx.log.info("Welcome plugin loaded!");

ctx.eventBus.on("player:login", (e) => { ctx.sendSystemMessage(e.playerId, "Welcome to the server!"); });

ctx.registerCommand("hello", (entityId, args) => { const name = args[0] ?? "adventurer"; ctx.sendSystemMessage(entityId, Hello, ${name}!); }, { description: "Greet someone", usage: "/hello [name]", }); }, };

export default plugin;

Your First Client Plugin

Create a new client plugin in the Plugin editor (or as a .js file):

ctx.log.info("Stats HUD loaded!");

var widgetId = ctx.createWidget({ html: buildHtml(ctx.getState()), anchor: "top-right", position: { x: 16, y: 80 }, width: 180, draggable: true, css: "background:rgba(0,0,0,0.75);" + "color:#eee;border:1px solid #555;" + "border-radius:8px;padding:10px;" + "font-family:monospace;font-size:12px;", });

ctx.onStateChange(function() { ctx.updateWidget(widgetId, { html: buildHtml(ctx.getState()), }); });

function buildHtml(state) { return "<div>" + "HP: " + state.selfHp + "/" + state.selfMaxHp + "<br>MP: " + state.selfMp + "/" + state.selfMaxMp + "<br>Gold: " + state.selfSilver + "</div>"; }

Plugin Structure

Every server plugin exports a PluginDefinition object:

interface PluginDefinition {
  name: string;          // Unique identifier
  version: string;       // e.g., "1.0.0"
  author?: string;
  description?: string;

register(ctx: PluginContext): void | Promise<void>; unregister?(): void | Promise<void>; }

The register function receives a PluginContext — this is the full API for interacting with the game server.

Auto-Cleanup

All subscriptions, commands, timers, and channels registered through ctx are automatically cleaned up when:

  • The server shuts down
  • The plugin is disabled or reloaded
The unregister() hook is optional and only needed for external resources.