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

    Dynamic properties

    Save plugin data on the world, on players and entities, on items and on blocks, plus tags for simple flags.

    Variables in a plugin are lost when Basalt stops or the plugin reloads. Dynamic properties are how a plugin saves data that lasts: small named values stored in the world save itself.

    You can attach them to four kinds of things:

    On Good for Saved
    world Global settings, counters, anything not tied to one player With the world
    A Player or other Entity Per-player data: balances, homes, statistics With the player’s or entity’s record
    An ItemStack Data that travels with one item: an owner, a serial number Inside the item
    A Block (Basalt) Data about one place: who placed a block, a locked chest’s owner In the block’s NBT, with the world

    A property has a string identifier and a value, which is one of:

    • a boolean,
    • a number (finite),
    • a string, up to 32767 bytes,
    • a Vector3 ({ x, y, z }).

    By convention, prefix identifiers with a namespace, such as shop:balance.

    import { world } from "@minecraft/server";
    world.afterEvents.worldLoad.subscribe(() => {
    const starts = (world.getDynamicProperty("stats:starts") as number | undefined) ?? 0;
    world.setDynamicProperty("stats:starts", starts + 1);
    console.log(`This world has been started ${starts + 1} times`);
    });

    getDynamicProperty returns undefined for a property that was never set, so always supply a default. Its return type is a union, so narrow it with typeof or a cast before using it.

    import { world, Player } from "@minecraft/server";
    function getCoins(player: Player): number {
    const coins = player.getDynamicProperty("shop:coins");
    return typeof coins === "number" ? coins : 0;
    }
    function addCoins(player: Player, amount: number): void {
    player.setDynamicProperty("shop:coins", getCoins(player) + amount);
    }
    world.afterEvents.playerSpawn.subscribe(({ player, initialSpawn }) => {
    if (!initialSpawn) return;
    if (player.getDynamicProperty("shop:firstJoin") === undefined) {
    player.setDynamicProperty("shop:firstJoin", Date.now());
    addCoins(player, 100);
    player.sendMessage("Welcome! You have been given 100 coins.");
    }
    });

    A player’s properties are stored in their saved record, so they are still there the next time the player joins.

    Mobs and other entities work the same way, and keep their properties for as long as the entity exists and is saved.

    A Vector3 is stored directly, which is handy for homes and checkpoints:

    import { Player } from "@minecraft/server";
    function setHome(player: Player): void {
    player.setDynamicProperty("homes:home", player.location);
    }
    function goHome(player: Player): void {
    const home = player.getDynamicProperty("homes:home");
    // A Vector3 is the only object a property can hold.
    if (typeof home === "object") player.teleport(home);
    else player.sendMessage("You have not set a home.");
    }

    Properties on an ItemStack are part of the item, so they follow it through inventories, chests and drops.

    import { ItemStack, Player } from "@minecraft/server";
    function giveKey(player: Player, door: string): void {
    const key = new ItemStack("minecraft:tripwire_hook", 1);
    key.nameTag = `Key to ${door}`;
    key.setDynamicProperty("keys:door", door);
    player.getComponent("minecraft:inventory")?.container.addItem(key);
    }
    function opens(item: ItemStack | undefined, door: string): boolean {
    return item?.getDynamicProperty("keys:door") === door;
    }

    Two things to know:

    • Only items that do not stack can hold properties. Setting one on a stackable item throws.
    • An ItemStack you read from an inventory is a copy. After changing its properties, write it back with container.setItem(slot, item).

    container.getSlot(slot) gives a ContainerSlot instead, a live handle on the slot. Its setDynamicProperty and clearDynamicProperties write the item back for you, and throw if the slot is empty.

    A Block can hold properties too. This is Basalt’s own: Mojang’s Block has no such methods, so a plugin that uses them only runs on Basalt.

    import { world } from "@minecraft/server";
    // Remember who placed each chest.
    world.afterEvents.playerPlaceBlock.subscribe(({ player, block }) => {
    if (block.typeId === "minecraft:chest") block.setDynamicProperty("locks:owner", player.name);
    });
    // Tell players whose chest they opened.
    world.afterEvents.playerInteractWithBlock.subscribe(({ player, block }) => {
    const owner = block.getDynamicProperty("locks:owner");
    if (typeof owner === "string" && owner !== player.name) {
    player.sendMessage(`This chest belongs to ${owner}.`);
    }
    });

    Basalt keeps the properties in the block’s NBT. They are saved with the world, move with the block when a piston pushes it, and go when the block is broken or replaced by another block. Air keeps nothing: setting a property on an air block does nothing.

    A block with no block entity of its own, such as stone, is given a bare one to carry the properties. It is not sent to players, and it is removed again when the last property is.

    Block properties take the same values as the others, and are private to your plugin in the same way. The methods are the same too: getDynamicProperty, setDynamicProperty, setDynamicProperties, getDynamicPropertyIds, getDynamicPropertyTotalByteCount and clearDynamicProperties.

    import { world } from "@minecraft/server";
    // Remove one property.
    world.setDynamicProperty("stats:starts", undefined);
    // Set several at once.
    world.setDynamicProperties({ "event:active": true, "event:round": 1 });
    // See what is stored.
    for (const id of world.getDynamicPropertyIds()) {
    console.log(id, world.getDynamicProperty(id));
    }
    console.log(`${world.getDynamicPropertyTotalByteCount()} bytes used`);
    // Remove everything this plugin stored on the world.
    world.clearDynamicProperties();

    The same methods exist on players, entities, item stacks and blocks.

    A plugin sees only the properties it set. Two plugins can both use stats:kills on the same player without colliding, and clearDynamicProperties only clears your own.

    Properties are filed under the header.uuid of your manifest.json. That has two consequences:

    • Do not change the UUID once a plugin is in use. With a new UUID it can no longer see its old data.
    • Plugins cannot share data through dynamic properties. To pass data between plugins, use scoreboards, tags or script events.

    There is no object or array type. Store structured data as a JSON string:

    import { Player } from "@minecraft/server";
    interface Stats {
    kills: number;
    deaths: number;
    }
    function loadStats(player: Player): Stats {
    const raw = player.getDynamicProperty("pvp:stats");
    return typeof raw === "string" ? JSON.parse(raw) : { kills: 0, deaths: 0 };
    }
    function saveStats(player: Player, stats: Stats): void {
    player.setDynamicProperty("pvp:stats", JSON.stringify(stats));
    }

    Keep the 32767-byte limit on strings in mind. For large collections, split the data across several properties.

    For simple yes/no flags, tags are lighter than properties. A tag is just a string attached to a player or entity.

    import { world } from "@minecraft/server";
    world.afterEvents.playerSpawn.subscribe(({ player, initialSpawn }) => {
    if (!initialSpawn) return;
    if (!player.hasTag("returning")) {
    player.addTag("returning");
    player.sendMessage("First time here? Type /help.");
    }
    });
    // Everyone with a tag.
    const vips = world.getDimension("overworld").getPlayers({ tags: ["vip"] });

    Unlike dynamic properties, tags are shared: every plugin sees the same tags, operators can change them with /tag, and command selectors can match them with tag=. That makes tags a good way for plugins and commands to cooperate.

    Method Does
    addTag(tag) Adds a tag. Returns false if it was already there.
    removeTag(tag) Removes a tag.
    hasTag(tag) Tests for a tag.
    getTags() Lists all tags.

    Reading a property is a read like any other, so the rule about reads inside before events applies. If a before-event handler needs a player’s data to decide whether to cancel, load it into a Map when the player spawns and consult the Map in the handler.