Run Minecraft Bedrock addons (.mcaddon / .mcpack) on a PowerNukkitX
server. Drop the files in, restart, and everything is live: items, blocks,
entities, recipes, loot tables, spawn rules, AI, creative menu groups, the
resource pack served to players, and the @minecraft/server scripts, actually
executed by an embedded JavaScript engine.
No conversion, no manual BP/RP splitting, no code to write.
plugins/AddonBridge/addons/
|- Rustic Furniture.mcaddon
|- Free Fire Gun 1.9.mcaddon
`- The Lost Age.mcaddon- Put the jar into
plugins/. - Start once (this creates
plugins/AddonBridge/addons/andconfig.yml). - Drop your
.mcaddonfiles in there. - Restart.
AddonBridge accepts .mcaddon, .mcpack, .zip and already extracted folders,
including nested ones: a .mcaddon containing two .mcpack files is handled on
its own. Archives are extracted into plugins/AddonBridge/cache/ and only
re-extracted when their content changes, so later restarts are immediate.
On startup:
[AddonBridge]: Addons detected: 14 packs (7 BP, 7 RP).
[AddonBridge]: Load finished in 3 s: 10232 items, 420 blocks, 29 entities,
10126 recipes, 11 loot tables, 16 spawn rules,
40 creative groups, 7 resource packs.
[AddonBridge]: Scripts active: 5 modules, 10 custom components.
/addons (alias /addonbridge), permission addonbridge.command (op):
| Command | Effect |
|---|---|
/addons list |
List the loaded addons and their packs (BP / RP) |
/addons status |
Counters from the last load |
/addons scripts |
JS engine state plus any missing @minecraft/server API reached |
/addons info <name> |
Details for one addon: version, uuid, scripts |
/addons check |
Cross checks the behavior packs against the resource packs: item icons, geometries, armor attachables, texture files, client entity definitions. Add all to also list the block textures that come from vanilla rather than from an addon pack |
/addons check exists because nothing in this pipeline fails loudly when a
resource pack is incomplete: a missing icon key just renders as the purple and
black checkerboard, an armor with no attachables/ entry equips but shows
nothing on the player, and an entity with no entity/**.json client definition
spawns invisible. The command names every one of them.
| Behavior pack content | Status |
|---|---|
items/**.json |
Full items: icon, name, durability, damage, armor, enchantability, digger, food, fuel, block placer, shooter/throwable, cooldown, tags, repairable, render offsets, mining speed |
items with minecraft:wearable |
Real armor: the slot is inferred from the enchant slot when it is missing, minecraft:armor merges into the protection value, and /addons check tells you whether the matching attachable exists |
items with minecraft:digger |
destroy_speeds evaluated like the client, Molang conditions included ("1", q.any_tag(...), &&, ` |
blocks/**.json |
Full blocks: geometry, material instances, collision and selection boxes (a list of boxes becomes their union instead of a full cube), integer and string states, permutations, tags, hardness, light, friction, map color |
blocks with the minecraft:placement_direction trait |
The block faces the player who places it |
entities/**.json |
Entities: health, collision box, speed, attack, families, physics, spawn egg, /summon, plus rideable seats, saddle, ride controls, horse jump strength, tameable, ageable, breedable, healable, home, inventory, equippable, boostable, item controllable |
entities component_groups and events |
Applied at runtime: add / remove, sequence, weighted randomize, trigger chains, run_command, and the common filters. The active groups are saved with the entity |
entities with minecraft:rideable |
Interacting mounts the player on the declared seat, whether the component sits in the base components or in a group an event turns on later |
entities with minecraft:interact |
Right clicking with the declared item fires the event, plays the sound and consumes the item |
entities with minecraft:timer, spawn_entity, despawn, damage_sensor, experience_reward |
Ticked at runtime: countdowns fire their event, spawners produce their entity, damage immunity is honoured |
entities with minecraft:behavior.* |
AI translated into PowerNukkitX behaviors, with the route finder and controllers chosen from minecraft:navigation.* / minecraft:movement.*, so a flying or swimming mob no longer walks a ground path. Covers roaming, melee and ranged attack, targeting by family, retaliation, fleeing, panic, tempt, breeding, following, looking, hovering |
loot_tables/**.json |
Block and entity drops: pools, rolls, weights, set_count, set_damage, set_data, enchant_randomly, looting_enchant, specific_enchants, and the killed_by_player, random_chance, match_tool conditions |
spawn_rules/**.json |
Natural spawning: surface, underground, underwater, brightness, height, biome, density, herd size, weight |
recipes/**.json |
recipe_shaped, recipe_shapeless, recipe_furnace |
item_catalog/**.json |
Custom creative menu groups |
scripts/**.js |
Executed, see the next section |
| Resource pack | Zipped and served to players, flagged as an addon pack |
Components PowerNukkitX does not model yet are forwarded as is to the client
(passthrough-unknown-components), so rendering stays correct even for recent
components.
PowerNukkitX has no JavaScript engine, so AddonBridge embeds one: GraalJS,
full ES2023 including ES modules, classes, async/await, optional chaining and
private fields. Scripts run on the main thread, like on Bedrock, and are
loaded once the worlds are ready.
The implemented API covers the core of @minecraft/server:
| Area | Implemented |
|---|---|
world |
getDimension, getAllPlayers, getEntity, sendMessage, getAbsoluteTime, getTimeOfDay/setTimeOfDay, getDay, dynamic properties, beforeEvents/afterEvents, scoreboard (read) |
system |
run, runTimeout, runInterval, runJob (generators, sliced per tick), clearRun, currentTick, waitTicks, beforeEvents.startup |
Dimension |
getBlock, getTopmostBlock, setBlockType, setBlockPermutation, spawnEntity, spawnItem, getEntities/getPlayers with the full filter set (type, families, tags, their exclude* counterparts, minDistance/maxDistance, closest/farthest), runCommand(Async), createExplosion, playSound |
Entity / Player |
Position, rotation, velocity, teleport, getComponent, getComponents, triggerEvent, effects, tags, applyDamage, kill, runCommand, dynamic properties, nameTag, onScreenDisplay, game mode, XP, getBlockFromViewDirection, playSound, playMusic |
Block |
typeId, permutation, setType, setPermutation, above/below/north/offset, getComponent("inventory"), tags |
BlockPermutation |
resolve, getState, withState, getAllStates, matches, clone, getItemStack |
ItemStack |
Constructor, typeId, amount, nameTag, getLore/setLore, clone, getComponent |
| Components | health, inventory with Container and ContainerSlot, equippable, movement, durability, rideable (getRiders, addRider, ejectRider(s), seats), type_family, variant, mark_variant, scale, tameable, is_baby, onfire, leashable |
| Events | playerSpawn, playerJoin, playerLeave, playerBreakBlock, playerPlaceBlock, playerInteractWithBlock, playerInteractWithEntity, itemUse, entityHurt, entityHitEntity, entityDie, entitySpawn, projectileHitBlock, projectileHitEntity, scriptEventReceive, chatSend. The beforeEvents variants are cancellable |
| Custom components | blockComponentRegistry and itemComponentRegistry: onPlayerInteract, onPlace, onPlayerDestroy / onBreak, onStepOn, onStepOff, onEntityFallOn, onTick, onRandomTick, onUse, onUseOn, onMineBlock, onHitEntity |
| Sounds | playSound on world, Dimension and Player sends the name straight to the client, so a sound declared in the addon sound_definitions.json plays like any vanilla one |
@minecraft/server-ui |
ActionFormData, MessageFormData, ModalFormData mapped onto PowerNukkitX forms |
The API is not exhaustive. When a script reaches an unimplemented method,
AddonBridge names it in the console on startup and through /addons scripts,
instead of failing silently. That is how you find out exactly what is left to
add for a given addon.
trading/,dialogue/, server sideanimation_controllers.- Molang beyond what the digger conditions and entity filters need: no full
expression engine, so a
permutationscondition still reaches the client as written rather than being evaluated server side. minecraft:transformation,minecraft:environment_sensorandminecraft:bossare read but not acted on.- Vanilla entity overrides (
minecraft:zombieand friends) are ignored. - An obfuscated addon loads and runs, but resolves API names dynamically,
which is where
/addons scriptshelps most.
An item whose identifier already belongs to a block of the same addon is skipped: that is the block item, already created when the block was registered.
plugins/AddonBridge/config.yml:
| Key | Default | Effect |
|---|---|---|
enabled |
true |
Turn the plugin off entirely |
addons-folder |
addons |
Scanned folder, relative to the plugin folder |
serve-resource-packs |
true |
Send addon resource packs to players |
creative-menu |
true |
Add content to the creative menu, see below |
register-items / -blocks / -entities / -recipes / -creative-groups |
true |
Enable each content type |
register-loot-tables |
true |
Drops driven by the addon loot tables |
register-entity-ai |
true |
Translate minecraft:behavior.* into PowerNukkitX AI |
run-scripts |
true |
Execute the @minecraft/server scripts |
passthrough-unknown-components |
true |
Forward unmodelled components to the client |
apply-placement-traits |
true |
Face blocks towards the player placing them |
force-reextract |
false |
Re-extract archives on every startup |
disabled |
[] |
File or folder names to ignore, without extension |
log-level |
normal |
quiet, normal or verbose |
creative-group-per-addon (on by default) gives each addon its own creative
groups. A pack with thousands of tools declares the vanilla group names
(itemGroup.name.pickaxe and friends), so its 1021 pickaxes land inside the
vanilla pickaxe group and your iron pickaxe disappears among them. With the
option on, that pack gets a Blocks Armor Tools Pickaxe group of its own and
the vanilla groups stay clean. A pack shipping its own item_catalog already
declares real groups and is left alone.
Addon items and blocks are registered sorted by creative category and group. PowerNukkitX appends every custom entry to the end of the creative list while tagging it with its own group index, and a Bedrock client only draws a group as a group when its entries are next to each other. Registering pack by pack therefore produced one axe, then one helmet, then one pickaxe, and so on: a large addon turned the creative menu into an unsorted wall. Sorting first keeps each group in one block.
PowerNukkitX rescans the entire creative menu on every item added, which makes filling it quadratic. Measured on this server with the same addons:
| Content | creative-menu: true |
creative-menu: false |
|---|---|---|
| 6 addons, 442 items and blocks | 1 s | 1 s |
| Plus one addon with 10232 items | 279 s | 3 s |
The true default is right in almost every case. Switching to false removes
nothing: content stays placeable, craftable and obtainable with /give, it just
no longer shows up in the creative tab, and custom creative groups are skipped
since they would be empty. AddonBridge prints the hint on its own past 3000
entries.
During onLoad, before PowerNukkitX freezes its registries, the plugin:
- Extracts archives into
cache/extracted/with a SHA-256 fingerprint, zip slip protection and the same GBK encoding fallback PowerNukkitX uses. - Finds every
manifest.jsonand classifies the pack as BP or RP. - Translates each JSON into a
CustomItemDefinition,CustomBlockDefinitionorCustomEntityDefinition. - Generates one Java class per identifier at runtime with ASM and
MethodHandles.Lookup.defineClass, because the PowerNukkitX registries register by class and not by instance. That includes real Java enums for string block states. - Re-zips each resource pack into
cache/resourcepacks/and adds it to theResourcePackManagerthrough a dedicated loader.
Then, once the worlds are loaded (POSTWORLD), it starts the GraalJS context,
publishes @minecraft/server as a virtual ES module backed by a Java bridge,
evaluates each pack script entry, emits system.beforeEvents.startup and hooks
its scheduler onto the server tick.
ASM comes from the server jar. GraalJS is bundled into the plugin jar, around 35 MB, which makes it self contained on a standard JDK 21.
./build.sh./build.sh --deployThe PowerNukkitX jar is a compileOnly dependency: the local dev server one by
default, otherwise LINESIA_PNX_JAR=/path/to/powernukkitx.jar.