Skip to content
BasaltSoftware
Bedrock 1.26.60

Site colour

Discord
Esc

    ↑↓ moveEnter openEsc close

    Keyboard shortcuts
    Ctrl K
    Search the whole site, from any page
    /
    Jump to the sidebar search, or search the whole site where there isn't one
    Enter
    Open the top search result
    Esc
    Clear the search, or close this panel
    ?
    Show these shortcuts
    Plugin API

    Custom commands

    Register your own slash commands with typed parameters, enums and permission levels.

    Plugins add slash commands through the custom command registry, the same API Mojang’s scripting uses. A registered command shows up in the client’s command list with auto-complete, in /help, and can be run from the terminal.

    Commands can only be registered during startup. Subscribe to system.beforeEvents.startup and use the customCommandRegistry it gives you:

    import { system, CommandPermissionLevel, CustomCommandStatus } from "@minecraft/server";
    system.beforeEvents.startup.subscribe(({ customCommandRegistry }) => {
    customCommandRegistry.registerCommand(
    {
    name: "example:ping",
    description: "Replies with pong",
    permissionLevel: CommandPermissionLevel.Any,
    },
    () => {
    return { status: CustomCommandStatus.Success, message: "Pong!" };
    },
    );
    });

    The name must have a namespace, written namespace:name. The command then answers to both the short and the full form: /ping and /example:ping.

    Registration throws a CustomCommandError if:

    • it happens outside the startup event,
    • the name has no namespace,
    • a command with that name is already registered, by Basalt or by another plugin.

    Because Basalt’s own commands are registered first, a plugin cannot replace a built-in command such as /give.

    The callback receives the command’s origin, followed by one argument per parameter, in the order they were declared. It returns a result:

    { status: CustomCommandStatus.Success, message: "Shown to whoever ran it" }

    message is optional. With CustomCommandStatus.Failure the message is shown as an error.

    The origin tells you who ran the command:

    Field Meaning
    sourceType CustomCommandSource.Entity for a player, CustomCommandSource.Server for the terminal.
    sourceEntity The player who ran it, or undefined from the terminal.

    Always handle the case where there is no player:

    import { system, CommandPermissionLevel, CustomCommandStatus, Player } from "@minecraft/server";
    system.beforeEvents.startup.subscribe(({ customCommandRegistry }) => {
    customCommandRegistry.registerCommand(
    {
    name: "example:heal",
    description: "Restores your health",
    permissionLevel: CommandPermissionLevel.GameDirectors,
    },
    (origin) => {
    const player = origin.sourceEntity;
    if (!(player instanceof Player)) {
    return { status: CustomCommandStatus.Failure, message: "Only players can use this." };
    }
    system.run(() => {
    player.getComponent("minecraft:health")?.resetToMaxValue();
    });
    return { status: CustomCommandStatus.Success, message: "Healed." };
    },
    );
    });

    Treat a command callback like a before event: inside it, a player is read as the plugin last saw them, and a first read can throw. Return the result straight away and do the real work in system.run, as the example above does. BDS requires the same, since it does not allow world changes inside a command callback.

    The result’s message is sent to the player when the callback returns, so a slow callback means a slow reply.

    Anything asynchronous, such as showing a form or making an HTTP request, must start from system.run too:

    system.run(() => void openShop(player));
    return { status: CustomCommandStatus.Success };

    Declare parameters in mandatoryParameters and optionalParameters. Each has a name, which the client shows as a hint, and a type.

    import {
    system,
    CommandPermissionLevel,
    CustomCommandParamType,
    CustomCommandStatus,
    Player,
    } from "@minecraft/server";
    system.beforeEvents.startup.subscribe(({ customCommandRegistry }) => {
    customCommandRegistry.registerCommand(
    {
    name: "example:announce",
    description: "Sends a title to players",
    permissionLevel: CommandPermissionLevel.GameDirectors,
    mandatoryParameters: [
    { name: "targets", type: CustomCommandParamType.PlayerSelector },
    { name: "text", type: CustomCommandParamType.String },
    ],
    optionalParameters: [
    { name: "seconds", type: CustomCommandParamType.Integer },
    ],
    },
    (origin, targets: Player[], text: string, seconds?: number) => {
    const stay = (seconds ?? 3) * 20;
    system.run(() => {
    for (const player of targets) {
    player.onScreenDisplay.setTitle(text, {
    fadeInDuration: 10,
    stayDuration: stay,
    fadeOutDuration: 10,
    });
    }
    });
    return { status: CustomCommandStatus.Success, message: `Sent to ${targets.length} players.` };
    },
    );
    });

    Run as /announce @a "Event starting" 5. Text with spaces must be quoted. An optional parameter that was left out arrives as undefined.

    CustomCommandParamType The callback receives
    Boolean true or false
    Integer A whole number
    Float A number
    String A string: one word, or a quoted phrase
    Enum One of the enum’s values, as a string
    Location A Vector3. Players can use ~ for relative coordinates.
    PlayerSelector An array of Player, from a name or a selector such as @a
    EntitySelector An array of Player. On Basalt, selectors in custom commands pick players only.
    BlockType, ItemType, EntityType The identifier as a string

    Basalt checks the arguments before calling you. If a number is not a number, or no player matches a selector, the player gets the usual error and the callback is not called.

    An enum is a fixed list of words that the client auto-completes. Register it before the command that uses it, and name it in the parameter with enumName:

    import {
    system,
    CommandPermissionLevel,
    CustomCommandParamType,
    CustomCommandStatus,
    ItemStack,
    Player,
    } from "@minecraft/server";
    const kits: Record<string, string[]> = {
    starter: ["minecraft:wooden_sword", "minecraft:bread"],
    miner: ["minecraft:iron_pickaxe", "minecraft:torch"],
    };
    system.beforeEvents.startup.subscribe(({ customCommandRegistry }) => {
    customCommandRegistry.registerEnum("example:kit", Object.keys(kits));
    customCommandRegistry.registerCommand(
    {
    name: "example:kit",
    description: "Gives you a kit",
    permissionLevel: CommandPermissionLevel.Any,
    mandatoryParameters: [
    { name: "kit", type: CustomCommandParamType.Enum, enumName: "example:kit" },
    ],
    },
    (origin, kit: string) => {
    const player = origin.sourceEntity;
    if (!(player instanceof Player)) {
    return { status: CustomCommandStatus.Failure, message: "Only players can take a kit." };
    }
    system.run(() => {
    const container = player.getComponent("minecraft:inventory")?.container;
    for (const item of kits[kit]) container?.addItem(new ItemStack(item, 1));
    });
    return { status: CustomCommandStatus.Success, message: `Here is the ${kit} kit.` };
    },
    );
    });

    Give enum names a namespace, like command names. Each name can be registered once.

    permissionLevel sets who may run the command.

    Level Who can run it
    CommandPermissionLevel.Any Every player
    CommandPermissionLevel.GameDirectors Operators and the terminal
    CommandPermissionLevel.Admin, Host, Owner The terminal only

    Players only see, and can only run, commands at or below their level. See Permissions, allowlist and bans for how players become operators.

    A plugin can run commands itself, in two ways with different rights.

    dimension.runCommand runs with the terminal’s rights, so it can run anything. ~ is relative to the world spawn. It throws if the command fails.

    import { world } from "@minecraft/server";
    world.getDimension("overworld").runCommand("time set day");

    player.runCommand runs the command as if the player had typed it, with the player’s own permission level. A command the player is not allowed to run will not work this way.

    import { world } from "@minecraft/server";
    for (const player of world.getAllPlayers()) {
    player.runCommand("me is ready");
    }

    Write the command without its leading slash. A player’s command is queued and runs after the call returns.

    To watch or block commands that players run, including built-in ones, use server.beforeEvents.playerCommand:

    import { server } from "@minecraft/server";
    server.beforeEvents.playerCommand.subscribe((event) => {
    console.log(`${event.player.name} ran: ${event.command}`);
    if (event.command.startsWith("me ")) event.cancel = true;
    });

    event.command is the line the player typed, without its leading slash. It can be changed to run something else instead.

    When plugins are reloaded with /reload, their commands are removed and registered again as each plugin starts.