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

    Differences from BDS

    What behaves differently from Bedrock Dedicated Server's scripting, what is missing and what Basalt adds.

    Basalt’s Plugin API is a port of Mojang’s scripting API: @minecraft/server 2.x, @minecraft/server-ui 2.x and @minecraft/server-net. Names and argument shapes are Mojang’s, so most scripts written for BDS run unchanged. This page covers the places where they do not, so you know what to check when porting a pack or writing one that must run on both.

    BDS Basalt
    Where scripts live A behaviour pack attached to the world A folder in plugins/ with a manifest.json
    TypeScript Compile to JavaScript first .ts files run directly; types are stripped, not checked
    @minecraft/server-net Needs a per-pack permissions file Available to every plugin unless script_net_enabled is off
    Module versions The manifest picks the API version One API is provided; the version in dependencies is not used to select anything

    Other loading rules:

    • A plugin can import only its own files and the four provided modules. There is no npm, and no importing another plugin’s files.
    • A dependency on a @minecraft/ module Basalt does not provide, such as @minecraft/server-gametest or @minecraft/server-admin, logs a warning, and importing it fails.
    • Each plugin runs in its own script engine on its own thread, so plugins share no globals.
    • /reload drops every subscription, timer and custom command and starts each plugin fresh.
    • Dynamic properties are stored per plugin, keyed by header.uuid.

    This is the biggest behavioural difference.

    On BDS, before events run in a restricted mode where the world can be read but not changed. On Basalt it is the other way round:

    • Changes are allowed inside before events and command callbacks. teleport, applyImpulse, sendMessage and the like are sent as the handler returns.
    • Reads are restricted. In block-break, block-place and mob-hurt before events, reading the world throws. In player before events and command callbacks, a player is read as the plugin last saw them, and a first read throws.

    Code that follows BDS’s rule works on both: read nothing you do not need in the handler, and wrap changes in system.run. Details and examples are in Events.

    Other differences in timing:

    • A before handler has 50 ms. Handlers that take more than 10 ms are reported in the log and in /status.
    • Reads outside before events ask the game and wait for the answer, for up to a second. Several reads of the same player or entity in one handler are answered from one snapshot until the handler changes something.
    • world.afterEvents.worldLoad fires on the first tick after startup, once the top-level code of every module has run. Basalt also fires a world.beforeEvents.worldLoad just before it. The old name worldInitialize works on both and logs a deprecation warning.

    Basalt has only the overworld.

    • world.getDimension("overworld") works. Any other id throws.
    • Entity.dimension is always the overworld.
    • A dimension passed in teleport options is accepted and ignored.
    • There is no playerDimensionChange event.
    • MinecraftDimensionTypes, the dimension ids as the older API exported them, is there for old packs. Only the overworld id gives a dimension.
    • player.runCommand runs the command as the player, with the player’s own permission level. A member cannot run operator commands this way. Use dimension.runCommand, which runs with full rights, or use the API directly.
    • dimension.runCommand throws when the command fails.
    • Only Basalt’s own commands exist. A script that runs /execute, /fill, /setblock, /tellraw or another command Basalt does not have will fail there. See Commands.
    • In custom commands, EntitySelector parameters resolve to players only.
    • CommandResult.successCount is 1 for a success rather than a count of targets.

    RawMessage objects are accepted wherever BDS accepts them, but a translate key is sent as the key itself rather than being translated. Prefer plain text.

    buttonPush, leverAction, pressurePlatePush, pressurePlatePop, targetBlockHit, tripWireTrip and pistonActivate fire, but Basalt does not track who caused them: source, player and sources are empty. pistonActivate gives the piston’s state as plain values, and getAttachedBlocks is not available.

    • Player.getVelocity() is the motion the client last reported.
    • playSound returns nothing; there is no sound instance to control.
    • Container.addItem does not report what did not fit.
    • Dimension.fillBlocks returns the number of blocks changed.
    • Dimension.spawnItem returns Basalt’s ItemEntity.
    • Dimension.getEntities includes players.
    • Dimension.getEntitiesFromRay and Entity.getEntitiesFromViewDirection treat each entity as a sphere around its middle, not its real hitbox, so a hit at the very edge can differ.
    • Dimension.getBlocks and containsBlock search at most 262,144 blocks. A larger volume, or one that reaches unloaded chunks, throws an UnloadedChunksError, or gives no blocks with allowUnloadedChunks. The filter’s includePermutations and excludePermutations are not read.
    • Entity.applyDamage ignores its options: the damage has no cause or attacker.
    • Block.getItemStack gives the item with the block’s own id, so the few blocks whose item has a different id are not mapped.
    • Entity.isSleeping, world.isHardcore and system.isEditorWorld are always false.
    • entityHitBlock does not send hitBlockPermutation. entityHealthChanged fires for mobs only when they take damage.
    • Custom item components registered through itemComponentRegistry only have onUse called, with no parameters argument.
    • inputInfo.touchOnlyAffectsHotbar is always false.
    • isSneaking, isSprinting, isSwimming and isClimbing are only meaningful for players.
    • In playerPlaceBlock, block is the block being placed at its position.
    • Container menus from @basalt/menus show workstation windows, but workstations in a menu do not craft.

    The typings in plugins/node_modules follow Mojang’s API closely so that existing code type-checks. They follow @minecraft/server 2.11.0 and declare several hundred classes, interfaces and members that Basalt does not implement. Using something that is not implemented fails at runtime: a missing method throws a TypeError when called, and a missing property reads as undefined.

    A few things are also missing from the typings although they work. See Events the typings do not list yet.

    Basalt is developed quickly and this list shrinks with each version. At the time of writing, the following are not implemented:

    Area Missing
    Players camera, fogSettings, locatorBar, graphicsMode, commandPermissionLevel, playerPermissionLevel, postClientMessage, aim assist, control schemes, the chat name parts, per-entity property overrides
    Entities playAnimation, tryTeleport, getAABB, getAllBlocksStandingOn, nameplateDepthTested, nameplateRenderDistance; most entity components beyond health, inventory, equippable, rideable, riding and item
    Blocks Block components (getComponent), and so block inventories; light levels, setWaterlogged and the other liquid queries; getParts
    Dimension cloneBlocks, light levels, biome queries, placeFeature, placeFeatureRule
    World setAbsoluteTime, the loot table manager, world clocks, pack settings, primitive shapes, the sound definition registry, aim assist
    Containers weight, containerRules
    Structures structureManager.createEmpty, and reading or writing blocks inside a Structure
    Scoreboard Teams; ScoreboardIdentity.getEntity() for entities
    Events entityHeal, playerButtonInput, playerInventoryItemChange, gameRuleChange, playerDimensionChange, dataDrivenEntityTrigger, container open and close events, and any other event the Event reference does not list
    Custom content Custom block components registered from scripts; for custom item components, every hook but onUse
    Modules @minecraft/server-gametest, @minecraft/server-admin, @minecraft/server-editor, @minecraft/debug-utilities

    If you depend on something less common, test it on Basalt before building on it. The reliable core is what the guides in this section use: the events listed in Events, players and their inventories, items, blocks, entities, scoreboards, dynamic properties, forms, commands and HTTP.

    These exist on Basalt and not on BDS. A plugin that uses them will not run on BDS.

    Addition Where
    The server object: beforeJoin, playerCommand, playerDie and entityDie before events, and server.plugin @minecraft/server
    Extra world events: playerHurt, playerMove, playerTeleport, playerRespawn, playerPickupItem, playerDropItem, playerToggleSneak, playerToggleSprint, playerChangeSkin, playerViolation, playerFormResponded, itemSpawn, itemComponentUse, and before events for playerEmote and worldLoad @minecraft/server
    knockback and cancelKnockback on hurt events @minecraft/server
    Player extras: kick, sendToast, addBossBar, clearEffects, locale, hidePlayer, showPlayer, canSee, setVelocity @minecraft/server
    ItemEntity, with owner, lifetime, pickup delay and per-player visibility @minecraft/server
    Dynamic properties on blocks: Block.getDynamicProperty, setDynamicProperty and the rest @minecraft/server
    Chunk control on Dimension: getChunk, loadChunk, unloadChunk @minecraft/server
    world.loadStructure and world.hasStructure @minecraft/server
    system.afterEvents.packetReceive and packetSend @minecraft/server
    CustomForm, uiManager.closeScreen, uiManager.setServerSettings @minecraft/server-ui
    WebSocket and HttpError @minecraft/server-net
    Container menus @basalt/menus

    Basalt’s additions are marked “Basalt’s own” in the typings’ documentation comments.

    1. Stick to Mojang’s API. Avoid everything in the table above, or guard it:

      import * as mc from "@minecraft/server";
      const onBasalt = "server" in mc;
    2. Inside before events and command callbacks, do not read the world, and wrap changes in system.run.

    3. Use world.afterEvents.worldLoad for setup.

    4. Use the 2.x form of ModalFormData options ({ defaultValue } objects).

    5. Avoid deprecated names. Basalt accepts runCommandAsync, world.say, system.runSchedule and worldInitialize for old packs, but logs a warning for each.

    6. Test on both. Basalt checks nothing at type level when it loads a plugin.