Wiki 9Minecraft Wiki 9Minecraft

Scripting and Editor Q&A Notes (September 2023)

Developer answers on Script API architecture, privilege execution, and custom components from late 2023.

5Sections
3Code examples
3Known errors

During the September 22, 2023 developer Q&A session, the Bedrock engineering team addressed key architectural questions regarding the transition from legacy Holiday Creator Features to the Script API and the state of the Bedrock Editor. These notes synthesize the technical patterns, engine constraints, and migration strategies established during that period. Reference this guide when upgrading older 1.19 or 1.20 packs to modern scripting standards or when troubleshooting read-only execution errors.

The Shift from JSON Events to Script API

A primary focus of the September 2023 discussions was the deprecation path for Holiday Creator Features, specifically targeting legacy JSON event responses inside blocks and items. Previously, creators relied on embedded Molang queries and event trees directly inside block and item files to run slash commands, trigger particles, or mutate block states. The engineering team clarified that complex state logic belongs in TypeScript or JavaScript via the `@minecraft/server` module, leaving JSON files strictly for declarative data definitions and static properties.

This separation of data and logic was designed to improve runtime performance and prevent the cascading lag spikes often caused by heavy Molang evaluations ticking across thousands of blocks. In the intermediate 1.20 releases before Custom Components were finalized, the recommended pattern was to subscribe to global `world.beforeEvents` and `world.afterEvents` listeners, filtering actions by block or item type IDs within script.

To set up a behavior pack that follows the late-2023 architecture, your `manifest.json` must declare a `script` module alongside the standard `data` module, and explicitly request the `@minecraft/server` dependency. If you are using features that have not yet graduated to stable, you must append the `-beta` suffix to the version string and enable the Beta APIs toggle on the world.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "Script Architecture Pack",
    "description": "Demonstrates late 2023 Script API configuration.",
    "uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "version": [1, 0, 0],
    "min_engine_version": [1, 20, 30]
  },
  "modules": [
    {
      "type": "data",
      "uuid": "b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e",
      "version": [1, 0, 0]
    },
    {
      "type": "script",
      "language": "javascript",
      "uuid": "c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f",
      "entry": "scripts/main.js",
      "version": [1, 0, 0]
    }
  ],
  "dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "1.6.0-beta"
    },
    {
      "module_name": "@minecraft/server-ui",
      "version": "1.1.0"
    }
  ]
}

Read-Only Execution and the System Run Pattern

One of the most frequent pain points raised by creators in the Q&A was the introduction of strict read-only execution contexts during `beforeEvents` callbacks. When the engine fires a `before` event—such as `playerBreakBlock` or `itemUseOn`—the simulation is currently mid-step. To maintain deterministic state and prevent crashes during multithreaded world evaluation, the engine locks world mutation methods during this phase.

During a `before` event, a script is permitted to read properties from the world and set the `.cancel` boolean on the event data object to true, which aborts the interaction before the client and server commit to it. However, attempting to spawn an entity, change a block permutation, modify inventory slots, or apply status effects directly inside that callback throws a restricted execution privilege error.

The architectural solution emphasized by the developers is deferring world mutations to the next available tick phase using `system.run()`. When you wrap state-changing code inside a `system.run()` callback, the engine queues that function to execute later in the tick after the read-only phase has concluded. This pattern allows you to cleanly cancel a vanilla behavior in the `before` event while still applying custom side effects immediately afterward.

BP/scripts/main.js
import { world, system, BlockPermutation } from "@minecraft/server";

world.beforeEvents.playerBreakBlock.subscribe((eventData) => {
  const { block, player, itemStack } = eventData;

  // Check if the player is breaking a custom block without an iron pickaxe
  if (block.typeId === "wiki:reinforced_stone") {
    if (!itemStack || itemStack.typeId !== "minecraft:iron_pickaxe") {
      // Canceling the event is allowed in read-only mode
      eventData.cancel = true;

      // World mutations and message sending must be deferred via system.run
      system.run(() => {
        player.sendMessage("You need an Iron Pickaxe to harvest this block.");
        block.dimension.spawnParticle("minecraft:villager_angry", block.location);
      });
    } else {
      // Even if not canceled, spawning extra entities or mutating adjacent blocks requires deferral
      const targetLocation = block.location;
      const targetDimension = block.dimension;

      system.run(() => {
        const aboveBlock = targetDimension.getBlock({
          x: targetLocation.x,
          y: targetLocation.y + 1,
          z: targetLocation.z
        });
        if (aboveBlock && aboveBlock.typeId === "minecraft:air") {
          aboveBlock.setPermutation(BlockPermutation.resolve("minecraft:cobblestone"));
        }
      });
    }
  }
});

Data Storage and Dynamic Properties Evolution

Another major topic in September 2023 was persistent data storage. Historically, creators abused scoreboard objectives attached to dummy players or stored serialized JSON strings inside entity tags to save state across world reloads. While functional, string-parsing tags every tick is slow, and scoreboards are limited to 32-bit signed integers.

The Script API answer to this problem is Dynamic Properties, accessed via `getDynamicProperty` and `setDynamicProperty` on the `world` object and individual `Entity` instances. Around the 1.20.30 and 1.20.40 cycle discussed in the Q&A, the engineering team was actively simplifying how dynamic properties work. Earlier versions required explicitly registering every property name, type, and byte length during the `worldInitialize` event using a `PropertyRegistry`. Feedback from the community highlighted that static registration made it difficult to create dynamic keys at runtime.

The updated approach discussed during this timeframe began removing the strict registration requirement in favor of direct getter and setter calls with built-in byte limits per string, alongside support for primitive types: strings, numbers, booleans, and `Vector3` objects. Despite these improvements, developers noted that scoreboard objectives remain useful when data needs to be read by command blocks, selector arguments (`scores={...}`), or UI text bindings that cannot directly query script memory.

Storage MechanismSupported TypesBest Use Case
Dynamic Propertiesboolean, number, string, Vector3Complex script state, persistent coordinates, and structured JSON strings scoped to entities or the world.
Entity Tagsstring (boolean presence)Fast filtering inside EntityQueryOptions and target selectors where both commands and scripts need access.
Scoreboards32-bit integerValues that must be displayed on the vanilla HUD or evaluated inside command selector arguments.

Preparing Blocks and Items for Custom Components

When creators asked how to replace `minecraft:on_interact` and `minecraft:on_step_on` in blocks without writing massive `if/else` chains in global event listeners, the team outlined the roadmap for Custom Components. Although Custom Components did not fully land in stable form until the 1.20.80 and 1.21.0 releases, the architectural blueprint was shared during these late-2023 sessions.

The goal was to reintroduce the modularity of JSON components without putting executable code back into the JSON files. Instead of writing Molang actions inside a block file, the JSON file simply lists the identifier of a custom component or relies on standard data-driven components for physical properties, while a script registers the functional implementation during world startup.

For blocks built around the 1.20.30 timeframe, the stable practice involves defining all static states, permutations, geometry, and material instances in the behavior pack JSON, while using states (`minecraft:placement_direction` or custom boolean/integer states) as the bridge between visual representation and script logic.

BP/blocks/reinforced_stone.json
{
  "format_version": "1.20.30",
  "minecraft:block": {
    "description": {
      "identifier": "wiki:reinforced_stone",
      "menu_category": {
        "category": "construction"
      },
      "states": {
        "wiki:active": [false, true]
      }
    },
    "components": {
      "minecraft:destructible_by_mining": {
        "seconds_to_destroy": 3.0
      },
      "minecraft:destructible_by_explosion": {
        "explosion_resistance": 15.0
      },
      "minecraft:geometry": "minecraft:geometry.full_block",
      "minecraft:material_instances": {
        "*": {
          "texture": "reinforced_stone_default",
          "render_method": "opaque"
        }
      }
    },
    "permutations": [
      {
        "condition": "q.block_state('wiki:active') == true",
        "components": {
          "minecraft:light_emission": 12,
          "minecraft:material_instances": {
            "*": {
              "texture": "reinforced_stone_active",
              "render_method": "opaque"
            }
          }
        }
      }
    ]
  }
}

Bedrock Editor Architecture and Extension Constraints

The second half of the Q&A focused on the Bedrock Editor, which remained in early experimental preview on Windows PC builds throughout late 2023. The team addressed several questions regarding how Editor extensions differ from standard gameplay behavior packs. While both use the TypeScript/JavaScript runtime and access `@minecraft/server`, Editor extensions also import the `@minecraft/server-editor` module to interact with the tool rail, modal panes, gizmos, and the undo/redo transaction manager.

A critical architectural rule for the Editor is the separation between Edit Mode and Crosshair (Gameplay) Mode. While in the Editor view, normal world simulation ticks (such as mob AI, random block ticks, and weather cycles) are paused or throttled so the level designer can manipulate the environment cleanly. Scripts written for Editor extensions must wrap block modifications inside transaction blocks so the creator can press Ctrl+Z to revert changes.

Developers also clarified why standard gameplay UI packs and `@minecraft/server-ui` forms do not automatically render inside the Editor's docking panes. The Editor utilizes its own UI property bag and pane-building API designed for keyboard-and-mouse productivity, whereas `@minecraft/server-ui` (`ActionFormData`, `ModalFormData`, `MessageFormData`) is built for in-game controller and touch navigation.

Module NameTarget EnvironmentPrimary Capability
@minecraft/serverGameplay & EditorCore world manipulation, entity spawning, block permutations, and tick scheduling.
@minecraft/server-uiGameplayPop-up modal forms, action buttons, and message boxes for players during active sessions.
@minecraft/server-editorEditor Only (PC)Custom tool rail icons, property panes, 3D selection volumes, and undo/redo transaction wrappers.

Which versions this applies to

Applies primarily to Bedrock Edition 1.20.30 through 1.20.50 where these transitions occurred, though the `system.run` and manifest patterns remain standard in 1.21+. Using `-beta` module versions in `manifest.json` requires enabling the 'Beta APIs' experimental toggle in world settings.

When it does not work

What you seeWhyThe fix
ReferenceError: Native function [Dimension::spawnEntity] does not have required privileges.You called a method that modifies the world state (such as spawning an entity, setting a block permutation, or modifying inventory) directly inside a `world.beforeEvents` callback while the engine is in read-only mode.Wrap the mutating method calls inside a `system.run(() => { ... })` block so the execution is deferred until the read-only phase ends later in the same tick.
Content Log Error: [Scripting][error]-Module [@minecraft/server] with version [1.6.0-beta] was not found.Either the 'Beta APIs' experimental toggle is turned off in the world settings, or the game client has updated to a newer release that dropped the older `-beta` revision.Enable 'Beta APIs' in the world settings under Experiments. If the game recently updated, change the version in `manifest.json` to match the current release's `-beta` version, or switch to a stable version string (e.g., `1.6.0` without `-beta`) if you only use stable APIs.
Dynamic property throws an error regarding unregistered property identifiers or exceeding byte limits.In versions prior to 1.20.40, calling `setDynamicProperty` without first defining the property in `worldInitialize` via `PropertyRegistry` causes a runtime exception. In newer versions, passing a string longer than the engine's maximum character byte limit triggers a failure.Update your pack's `min_engine_version` and `@minecraft/server` dependency to 1.20.40+ (`1.7.0` or newer) to use registration-free dynamic properties, and chunk large JSON payloads across multiple dynamic property keys.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.