EcmaCraft

Plugin Basics

Write handlers with decorators and register them in your entrypoint

EcmaCraft plugins are regular TypeScript modules with a default export function.

At runtime, EcmaCraft loads your compiled module and calls the default export with a PluginContext.

Minimal Plugin

import { Autocomplete, Command, Event, type PluginContext, type SpigotEventType } from 'ecmacraft';
import { CommandSender } from 'ecmacraft/spigot';

class GameplayHandlers {
  @Event('PlayerJoinEvent')
  onPlayerJoin(event: SpigotEventType<'PlayerJoinEvent'>) {
    const player = event.getPlayer();
    player.sendMessage('Welcome to the server!');
  }

  @Command('hello')
  onHelloCommand(sender: CommandSender, args: string[], label: string) {
    sender.sendMessage(`Hello from /${label} with ${args.length} arg(s)!`);
    return true;
  }

  @Autocomplete('hello')
  onHelloAutocomplete(sender: CommandSender, args: string[]) {
    const options = ['world', 'ecmacraft', 'plugin'];
    const lastArg = args[args.length - 1] ?? '';
    return options.filter((option) => option.startsWith(String(lastArg).toLowerCase()));
  }
}

export default function main(ctx: PluginContext) {
  ctx.registerHandlers(new GameplayHandlers());
}

How Handler Registration Works

  1. Decorators like @Event(...) attach metadata.
  2. ctx.registerHandlers(...) scans methods on your handler instance.
  3. Matching Java-side listeners/command bindings are wired at runtime.

If a method is not decorated, it is ignored during registration.

For commands, the method decorated with @Command(...) is the executor. Use @Autocomplete('same-command-name') to attach tab completion to that command.

Project Shape

For a standard plugin project:

  • src/main.ts is your entrypoint,
  • export default function main(ctx) is required,
  • ctx.registerHandlers(...) connects your decorated classes.

Best Practices

  • Keep event handlers small and focused.
  • Split large features into multiple handler classes.
  • Use TypeScript types (SpigotEventType<...>) for safer API usage.
  • Put gameplay constants/config in separate modules, not inside handler methods.
  • Return true from command handlers when the command is handled successfully.
  • Keep tab completion fast and side-effect free.

For detailed runtime behavior (cleanup callbacks, command normalization/collisions, timer execution order, and chalk styling), see Runtime API.

On this page