// Plugin development
lifecycle
start, stop, restart, install — commands and templating.
lifecycle
the lifecycle block declares named steps. each step has a command, an args array, and an optional useShell flag. commands are resolved at launch by rust.
templating
commands and args support {{userOverrides.*}} templating, resolved from the config form the host rendered (see config-schema):
jsonc"lifecycle": { "start": { "command": "{{userOverrides.java_path}}", "args": ["{{userOverrides.jvm_args}}", "-jar", "{{userOverrides.server_jar}}", "--nogui"] } }
runtime-qualified keys
some plugins need different commands per runtime. runtime-qualified keys win when an override matches. the dotted form start.<runtime> overrides the base start:
jsonc"lifecycle": { "start": { "command": "node", "args": ["{{userOverrides.entry}}"] }, "start.bun": { "command": "bun", "args": ["{{userOverrides.entry}}"] }, "start.deno": { "command": "deno", "args": ["run", "--allow-net", "{{userOverrides.entry}}"] }, "start.rust": { "command": "{{userOverrides.binary}}", "useShell": true } }
the discord bot plugin uses exactly this pattern across its four runtimes (node, bun, deno, rust).
useShell
set useShell: true to run the command through a shell. needed for some installers (forge) that expect shell semantics.
graceful shutdown
stop triggers a graceful shutdown with a 15-second timeout before hard-kill. this is deliberately tuned so active work completes — pending operations drain before teardown.
// warn
don’t put destructive commands in stop. the host will hard-kill after 15s if your command hasn’t returned, but you should design stop to return promptly (e.g. send a stop to the server stdin, not a kill -9).