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

    A Basalt plugin is a folder in plugins/ with a manifest.json and a script. It is laid out like the scripts of a Bedrock behaviour pack and written against the same API, so if you have written scripts for BDS this will look familiar.

    This guide builds a small plugin that greets players, then covers how Basalt loads plugins and how to get type checking.

    Start Basalt once. On first start it creates the plugins/ folder beside basalt.toml and fills it with what your editor needs:

    plugins/
    tsconfig.json
    node_modules/
    @minecraft/server/
    @minecraft/server-ui/
    @minecraft/server-net/
    @basalt/menus/

    The node_modules folders hold TypeScript typings for every module Basalt provides. They are rewritten on every start so they always match the version of Basalt you are running. tsconfig.json is written once and is yours to change.

    Make a folder for the plugin with a scripts/ folder inside it:

    plugins/
    hello/
    manifest.json
    scripts/
    main.ts
    {
    "format_version": 2,
    "header": {
    "name": "Hello",
    "description": "Greets players",
    "uuid": "b3a1f6c2-58d4-4e0b-9c37-1f2a6d8e4b90",
    "version": [1, 0, 0]
    },
    "modules": [
    {
    "type": "script",
    "language": "javascript",
    "uuid": "7e94c1d8-2a5b-4f63-8d10-c4b7a9e3f562",
    "version": [1, 0, 0],
    "entry": "scripts/main.ts"
    }
    ],
    "dependencies": [
    { "module_name": "@minecraft/server", "version": "2.0.0" }
    ]
    }

    What Basalt reads from it:

    Field Meaning
    header.name The plugin’s name in the log and in /status. Required.
    header.uuid Identifies the plugin’s saved data. Generate your own (for example with uuidgen) and never change it afterwards, or the plugin loses its dynamic properties.
    header.version Shown in the log. [1, 0, 0] or "1.0.0".
    modules Must contain one module with "type": "script". Its entry is the file run first, relative to the plugin folder. language, if present, must be "javascript", even when the entry is TypeScript.
    dependencies The modules the plugin uses. Basalt provides @minecraft/server, @minecraft/server-ui and @minecraft/server-net; a dependency on any other @minecraft/ module is logged as a warning.
    import { world } from "@minecraft/server";
    world.afterEvents.playerSpawn.subscribe((event) => {
    if (!event.initialSpawn) return;
    const { player } = event;
    player.sendMessage(`Welcome, ${player.name}!`);
    console.log(`${player.name} joined`);
    });

    playerSpawn fires every time a player spawns, including after dying; initialSpawn is true only for the spawn that follows joining.

    Start Basalt, or run /reload if it is already running. The log shows the plugin loading:

    Enabling Hello v1.0.0 (script)

    Join, and you are greeted in chat. console.log output appears in Basalt’s log under the plugin’s name.

    Plugins are ES modules. Split code across files and import them by relative path:

    scripts/greeting.ts
    export function greeting(name: string): string {
    return `Welcome, ${name}!`;
    }
    scripts/main.ts
    import { world } from "@minecraft/server";
    import { greeting } from "./greeting";
    world.afterEvents.playerSpawn.subscribe(({ player, initialSpawn }) => {
    if (initialSpawn) player.sendMessage(greeting(player.name));
    });

    An import can be written as ./greeting, ./greeting.ts or ./greeting.js; all three find greeting.ts. Importing a folder finds its index.ts or index.js.

    A plugin can import only:

    • its own files, inside its own folder,
    • @minecraft/server, @minecraft/server-ui and @minecraft/server-net,
    • @basalt/menus.

    There is no npm. To use a library, copy its source into the plugin folder.

    Basalt runs .ts files directly by stripping the types as it loads them. It does not type-check: code with type errors still runs, and fails at runtime if the error is real. Plain .js files work the same way, without the types.

    To get checking, open the plugins/ folder (not a single plugin) in your editor. The tsconfig.json there covers every */scripts/** file, and the typings in node_modules give you completion and documentation for the API.

    To check from the command line, with TypeScript installed:

    Terminal window
    npx tsc -p plugins

    This checks every plugin and writes no files.

    • Every folder in plugins/ that contains a manifest.json is a plugin. Plugins load in folder name order.
    • Each plugin runs in its own JavaScript engine on its own thread. A plugin that throws, loops forever or runs out of memory takes only itself down.
    • The entry file runs once, top to bottom. Top-level await works. Register your event handlers and commands there.
    • /reload unloads every plugin and loads them all again from disk. Event subscriptions, timers, custom commands and open forms are dropped, and system.beforeEvents.shutdown fires in each plugin first.

    Errors are logged under the plugin’s name with the file, line and column:

    hello/scripts/main.ts:7:12
    • A syntax error or a failed import stops that plugin from loading. Other plugins are unaffected.
    • An error thrown inside an event handler or timer is logged, and the plugin keeps running. An InvalidEntityError from a player or entity that has left is the exception: an event handler stops quietly, and a system.runInterval callback logs it once and is cleared.
    • Unhandled promise rejections are logged too.

    /status lists every plugin with its memory use, subscription count and any warnings. A plugin shown as stopped can be restarted with /reload.

    Game time is counted in ticks, 20 a second. system schedules work on the tick:

    import { system, world } from "@minecraft/server";
    // Once, on the next tick.
    system.run(() => world.sendMessage("Next tick"));
    // Once, after 5 seconds.
    system.runTimeout(() => world.sendMessage("Five seconds later"), 100);
    // Every minute, until cleared.
    const id = system.runInterval(() => world.sendMessage("Remember to vote!"), 1200);
    system.clearRun(id);
    // In an async function.
    async function countdown() {
    for (let left = 3; left > 0; left--) {
    world.sendMessage(`${left}...`);
    await system.waitTicks(20);
    }
    world.sendMessage("Go!");
    }

    setTimeout and setInterval also exist and count in milliseconds, independent of the tick.

    A plugin folder can also carry behaviour-pack content. Basalt reads items/, entities/ and loot_tables/ from the same folder, and sends a resource_pack/ folder to players. See Addons.