Wiki 9Minecraft Wiki 9Minecraft

Why Bedrock Has No Forge and What Takes Its Place

How Bedrock's native add-on engine replaces Java mod loaders with JSON components and the Script API.

6Sections
3Code examples
3Known errors

Java Edition modders moving to Bedrock often start by looking for Forge, Fabric, or NeoForge, only to discover that third-party mod loaders do not exist here. Instead of community-built bytecode injectors, Bedrock Edition ships with an official, built-in modding framework called Add-Ons. Understanding this architectural shift is the required first step before you can build custom blocks, items, entities, or scripted gameplay systems for Bedrock.

The Architectural Divide: C++ vs. Java Bytecode

Java Edition is written in Java, a language that compiles down to intermediate bytecode running on the Java Virtual Machine (JVM). Because JVM bytecode retains a rich amount of structural metadata and can be dynamically loaded or modified at runtime, community developers were able to build loaders like Forge and Fabric. These loaders decompile the game, map out its obfuscated internals, and use runtime injection frameworks like Mixins to rewrite vanilla classes in memory before the game even finishes booting.

Bedrock Edition is written in C++ and compiles directly into native machine code for each target platform: Windows, iOS, Android, Xbox, PlayStation, and Nintendo Switch. Native C++ binaries strip out class structures, method names, and type information during compilation, optimizing everything into raw memory addresses and assembly instructions. Hooking into a compiled C++ executable requires platform-specific memory injection, which is essentially the same technique used by cheat engines.

Even if you write a C++ memory hook for Windows 10, that compiled DLL will be completely useless on an Android phone running an ARM processor or a locked-down PlayStation console. Furthermore, console manufacturers and mobile app stores strictly prohibit apps from downloading and executing arbitrary native code for security reasons. Because Bedrock's core promise is seamless cross-platform multiplayer across all of these devices, a community-run, bytecode-patching mod loader like Forge is both technically impractical and prohibited by platform holders.

What Takes Its Place: The Native Add-On Engine

Because the community cannot patch the executable, Mojang built the modding API directly into the C++ engine itself. You do not ask players to install a separate loader profile; every copy of Bedrock Edition is already a mod loader out of the box. When a console player joins a PC player's world, the host automatically sends the required add-on files over the network, and the console runs them inside an isolated sandbox.

This native architecture splits every project into two distinct halves: the Behavior Pack (BP) and the Resource Pack (RP). The Behavior Pack runs on the logical server side and controls how the world works—entity AI, block properties, loot tables, recipes, functions, and scripts. The Resource Pack runs on the client side and handles everything the player sees and hears—models, textures, animations, particles, sounds, and user interface layouts.

Rather than compiling a single `.jar` file containing both code and assets, you distribute `.mcpack` or `.mcaddon` archives, which are simply standard ZIP files containing JSON files, scripts, and media assets. Every pack is registered with the engine through a single root configuration file named `manifest.json`.

Java Modding ConceptBedrock Add-On EquivalentKey Architectural Difference
Forge / Fabric LoaderBuilt-in C++ Add-On EngineRequires no external installation; works natively on consoles and mobile.
mods.toml / fabric.mod.jsonBP/manifest.json & RP/manifest.jsonUses versioned UUIDs to link server logic and client visuals separately.
Java Class InheritanceData-Driven JSON ComponentsObjects are assembled from pre-compiled C++ components rather than subclassed.
Forge Event Bus (@SubscribeEvent)Script API (@minecraft/server)Executes TypeScript/JavaScript inside a QuickJS sandbox on the server tick.
Sponge Mixins / CoremodsVanilla Override + Custom ComponentsYou cannot arbitrarily rewrite engine methods; you must use exposed hooks.

From Object-Oriented Inheritance to Entity-Component JSON

In a Forge mod, if you want to create a new block that acts like gravel but emits light and explodes when stepped on, you might create a new Java class that extends `FallingBlock`, override a few methods, and register an instance of your class during initialization. You are working inside an Object-Oriented Programming (OOP) hierarchy.

Bedrock replaces OOP inheritance with an Entity-Component-System (ECS) architecture driven entirely by JSON files. The C++ engine contains hundreds of pre-written, optimized components for blocks, items, and entities. To create a custom block, you do not write a class; you create a JSON file in your Behavior Pack and attach components like `minecraft:destructible_by_mining`, `minecraft:light_emission`, and `minecraft:geometry`.

The engine reads your JSON file on world load and assembles the block's behavior in C++ memory. If you want a zombie to shoot arrows like a skeleton, you do not need to write code—you copy the ranged attack component from the skeleton's behavior JSON into your custom entity's JSON. For dynamic math inside these JSON files—such as calculating an animation angle based on movement speed—Bedrock provides Molang, a lightweight expression language designed specifically for fast math evaluation inside components.

BP/blocks/glowing_obsidian.json
{
  "format_version": "1.20.80",
  "minecraft:block": {
    "description": {
      "identifier": "wiki:glowing_obsidian",
      "menu_category": {
        "category": "construction"
      }
    },
    "components": {
      "minecraft:destructible_by_mining": {
        "seconds_to_destroy": 15.0
      },
      "minecraft:destructible_by_explosion": {
        "explosion_resistance": 1200.0
      },
      "minecraft:light_emission": 12,
      "minecraft:map_color": "#5c1212"
    }
  }
}

Replacing the Forge Event Bus: The Bedrock Script API

Early Bedrock add-ons could only recombine existing vanilla behaviors in JSON, which frustrated Java developers accustomed to writing procedural logic. Today, Bedrock solves this with the Script API. Built on an embedded JavaScript engine (QuickJS), the Script API lets you write modern JavaScript or TypeScript that interacts directly with the game world on every server tick.

If you are familiar with Forge's `@SubscribeEvent` annotations or Fabric's event callbacks, the `@minecraft/server` module will feel immediately familiar. You can subscribe to `world.afterEvents` to react to things that have already happened, or `world.beforeEvents` to intercept, modify, or cancel an action before the engine commits to it. You can also bind custom script logic directly to specific blocks and items using the Custom Components system (`blockComponentRegistry` and `itemComponentRegistry`).

Before your script can run, you must declare a script module and a dependency on `@minecraft/server` inside your Behavior Pack's `manifest.json`. Notice how the manifest explicitly locks into a specific version of the API module; this ensures that when Mojang updates the game six months later, your script continues to run against the exact API surface it was written for.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "Forge to Bedrock Example BP",
    "description": "Demonstrates Script API setup in manifest",
    "uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "version": [1, 0, 0],
    "min_engine_version": [1, 21, 0]
  },
  "modules": [
    {
      "type": "data",
      "uuid": "b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e",
      "version": [1, 0, 0]
    },
    {
      "type": "script",
      "language": "javascript",
      "uuid": "c3d4e5f6-a7b8-4c5d-8e9f-2a3b4c5d6e7f",
      "entry": "scripts/main.js",
      "version": [1, 0, 0]
    }
  ],
  "dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "1.11.0"
    }
  ]
}

Writing Procedural Logic in @minecraft/server

Once your manifest points to `scripts/main.js`, the engine loads and executes that file when the world initializes. In Java, you might use a Mixin or a Forge `BlockEvent.BreakEvent` to stop a player from breaking a block under certain conditions. In Bedrock, you subscribe to `world.beforeEvents.playerBreakBlock`.

One important rule coming from Java concerns thread safety and execution timing. When you intercept a `beforeEvent`, the C++ engine is currently in the middle of processing a world tick and has locked the world state. You can set `event.cancel = true` immediately during that callback, but if you want to modify the world—such as spawning an entity, changing a block, or giving the player an item—you must defer that mutation to the main tick execution phase using `system.run()`. Attempting to mutate world state directly inside a `beforeEvent` callback will throw a read-only mode exception.

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

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

  if (block.typeId === "wiki:glowing_obsidian") {
    const isDiamondPick = itemStack && itemStack.typeId === "minecraft:diamond_pickaxe";

    if (!isDiamondPick) {
      // Cancel the block break immediately during the beforeEvent phase
      event.cancel = true;

      // Defer world/entity state changes to the standard tick phase
      system.run(() => {
        player.sendMessage("§cYour tool rebounds off the Glowing Obsidian!");
        player.applyKnockback(0, 0, 0, 0.5);
      });
    }
  }
});

What You Gain and What You Give Up

Moving from Java's unrestricted bytecode access to Bedrock's sandboxed API requires a shift in how you design features. In Java, if the vanilla game does not support custom gravity vectors or custom shader pipelines for a specific dimension, you can write a Mixin to rip out the vanilla renderer or physics loop and replace it with your own. In Bedrock, if Mojang has not exposed a hook via JSON components, Molang, Render Controllers, or the Script API, you cannot force the C++ engine to do it. You cannot write custom OpenGL/DirectX shaders from a Behavior Pack, nor can you access the host machine's file system or open arbitrary network sockets from a standard world.

Instead of fighting the engine, experienced Bedrock creators combine systems creatively. If a block needs complex tick behavior or an inventory UI that JSON blocks do not natively support, creators pair a custom block with an invisible, non-ticking marker entity that holds an inventory component, or build a custom chest UI or modal dialog using `@minecraft/server-ui`.

In exchange for living within this sandbox, you gain massive advantages in stability and reach. Because every JSON file uses a `format_version` key and every script locks its API dependency version, Mojang maintains backwards compatibility at the engine level. A well-built Bedrock add-on will often survive years of major game updates without needing to be recompiled, whereas a Forge mod breaks the moment Mojang updates obfuscation mappings or bumps the game version. Better yet, the moment you finish your `.mcaddon` file, it works immediately on PCs, phones, tablets, and consoles without a single line of platform-specific code.

Which versions this applies to

Applies to all modern Bedrock Edition versions (1.20.80 and 1.21+). The core JSON component architecture and the stable @minecraft/server Script API shown on this page work in normal worlds without enabling any Experimental toggles. Only bleeding-edge API methods marked with a '-beta' version suffix require turning on the 'Beta APIs' experimental toggle in world settings.

When it does not work

What you seeWhyThe fix
Content Log Error: [Scripting][error]-Module [@minecraft/server] with version [1.x.0-beta] is not found or does not match available versions.You either specified a '-beta' version in BP/manifest.json without enabling the 'Beta APIs' experimental toggle on the world, or you typed a stable version number higher than what your current game client supports.Use a stable version string (such as '1.11.0' for 1.21.0) in your manifest dependencies so your pack works without experiments, or turn on the 'Beta APIs' toggle in the world's Experiments menu if you specifically need beta features.
Scripting error in Content Log: ReferenceError: Cannot modify the world in read-only mode (beforeEvents).Coming from Forge events, you tried to spawn an entity, change a block, or modify a player's inventory or health directly inside a world.beforeEvents callback while the C++ engine has the tick state locked.Keep event.cancel = true inside the beforeEvents callback, but wrap any world or entity mutations inside a system.run(() => { ... }) block so they execute on the main tick phase immediately after.
Pack fails to import with 'Duplicate pack detected' or the script module never runs at all.You copied a manifest.json from another tutorial or template without generating new UUIDs, or your header UUID and module UUIDs are identical.Generate three completely unique UUID v4 strings for your BP/manifest.json: one for the header, one for the 'data' module, and one for the 'script' module.

Reference, straight from the game files

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