Skip to content

Commands

github-actions[bot] edited this page Sep 7, 2026 · 1 revision

Commands

exlib owns a single shared command root for the whole family: /exmod on the server and .exmod on the client. Every mod's sub-commands hang off it, so players learn one command. This page covers the built-in sub-commands and how to add your own.

The exmod root

ExmodCommand ([CommandRegister(Side = Universal)]) registers the root on both sides. On its own it just prints help; the useful behaviour lives in sub-commands. exlib ships these:

Command Side What it does
/exmod config [<mod> [<value> [<new>]]] server Edit any manageable config value live.
/exmod recipes [<mod> [<level>]] server Switch a mod's recipe-cost level.
/exmod verify [<mod>] server Run the content checks - dangling codes, missing lang, pinned network nodes - for one mod or all.
/exmod heal server Sweep loaded chunks and recreate orphaned block entities (healing).
.exmod network hi / .exmod network unhi client Toggle the transparent per-network colour highlight (block networks).
.exmod measure [metric|imperial] client Switch the display unit system (preference).

/exmod config and /exmod recipes are mod-agnostic: any mod that registers a manageable config or a recipe profile shows up automatically, no per-mod command code.

Adding your own sub-command

Implement IExSubCommand, tag it with the side, and point ParentName at "exmod":

[SubCommandRegister(Side = EnumAppSide.Server)]
public sealed class StatusSubCommand : IExSubCommand
{
    public string ParentName => "exmod";
    public void Register(ICoreAPI api, Mod mod, IChatCommand parent) =>
        parent.BeginSubCommand("status").HandleWith(args => TextCommandResult.Success("ok")).EndSubCommand();
}

You'll usually also want .WithDescription(Lang.Get(mod.Info.ModID + ":command-status-desc")) and, for anything that changes server state, .RequiresPrivilege(Privilege.controlserver) - both chain onto BeginSubCommand the same way.

A client-side sub-command casts api to ICoreClientAPI and is gated with [SubCommandRegister(Side = EnumAppSide.Client)]. The registry creates the exmod parent if it doesn't exist yet, so order between mods doesn't matter.

Register your sub-commands with CommandRegistry.RegisterAll(api, Mod, GetType().Assembly) - see Registries.

Your own /exmod sub-command for a registry

/exmod config and /exmod recipes are both "list every code, show one, set something on it" over an ExKeyedRegistry. Derive RegistrySubCommand<T> instead of IExSubCommand to get that for free:

public sealed class MyThingsSubCommand() : RegistrySubCommand<MyThing>(
    "mythings", "mymod:command-mythings-desc",
    () => MyThings.Codes, code => MyThings.TryGet(code, out var t) ? t : null) {
    protected override string Describe(MyThing t) => t.Code;
    protected override TextCommandResult Set(MyThing t, string[] args) => /* ... */;
    // NoneRegisteredKey, ListHeaderKey, UnknownCodeKey: three more lang-key overrides, same shape.
}

Set sees the words typed after the code - none of them means "show this entry", so a command that needs no separate show step can treat that case as the read. .exmod measure is not built this way: it edits one preference chosen at registration, not an entry picked from a registry by code, so it stays a plain IExSubCommand alongside verify, heal and network.

VTML pitfall in command output

Chat output is rendered as VTML. A literal < opens a tag and silently eats the rest of the message, so a usage string like Usage: /exmod config <mod> truncates at <mod>. Use square brackets instead - Usage: /exmod config [mod] - in any command-result or usage text. (Vanilla's <hk> is a hotkey-code tag and renders ? for command strings, so don't use it to wrap commands; prefer <strong>.)

Related pages

Clone this wiki locally