Linking Manifests with Dependencies
Every add-on starts with two manifest files: one in the Behavior Pack and one in the Resource Pack. By default, these two packs are completely unaware of each other. A player can apply the Behavior Pack to a world and forget the Resource Pack, resulting in invisible entities or checkerboard blocks. To prevent this, you create a hard link between the packs using the dependencies array inside the manifest.
A dependency link requires the exact header UUID and version array of the target pack. Notice that you link to the header UUID, never the module UUID. When a Behavior Pack lists a Resource Pack UUID in its dependencies, Minecraft will automatically activate the Resource Pack whenever the Behavior Pack is added to a world, provided both packs are installed in the client cache.
Inside the exact same manifest, the dependencies array serves a second linking purpose: connecting your code to native Script API modules. Unlike pack-to-pack links which use a UUID key, script module links use the module_name key alongside a specific semantic version string.
| Field | What it does | Notes |
|---|---|---|
| header.uuid | Uniquely identifies the pack itself | This is the UUID other packs reference in their dependencies block. |
| modules.uuid | Uniquely identifies the internal module | Never put this UUID into another pack's dependencies array. |
| dependencies.uuid | Links to another pack's header.uuid | Accepts a three-integer version array matching the target header. |
| dependencies.module_name | Links to a built-in native API module | Uses a string version (like "1.10.0") instead of an integer array. |
{
"format_version": 2,
"header": {
"name": "pack.name",
"description": "pack.description",
"uuid": "8a5b1e90-3c42-4f1a-9b11-029384756abc",
"version": [1, 0, 0],
"min_engine_version": [1, 20, 80]
},
"modules": [
{
"type": "data",
"uuid": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
"version": [1, 0, 0]
},
{
"type": "script",
"language": "javascript",
"uuid": "9f8e7d6c-5b4a-3f2e-1d0c-9b8a7f6e5d4c",
"entry": "scripts/main.js",
"version": [1, 0, 0]
}
],
"dependencies": [
{
"uuid": "f0e1d2c3-b4a5-6978-8071-625344352617",
"version": [1, 0, 0]
},
{
"module_name": "@minecraft/server",
"version": "1.10.0"
}
]
}The Identifier Bridge Between Packs
Once the packs are linked at the manifest level, individual assets must be linked across the Behavior Pack and Resource Pack divide. Minecraft does not use file paths or folder structures to match a server-side behavior definition to its client-side visuals. You can name your files whatever you like and nest them in sub-folders. The engine joins them solely through the description.identifier string.
This identifier must follow the namespace:name format, using lowercase letters, numbers, and underscores. The namespace is your custom project prefix, and it cannot be minecraft unless you are intentionally overriding a vanilla actor or block. When the server spawns an entity with a specific identifier, it broadcasts that exact string to the client. The client then searches its loaded Resource Packs for a client_entity file declaring the exact same description.identifier.
If there is a single character mismatch between the Behavior Pack identifier and the Resource Pack identifier, the link breaks. The entity will still exist in the world, take damage, and run its AI goals, but it will be completely invisible and silent because the client has no visual definition attached to that string.
Shortname Links Inside Client Entities
After the client locates the correct client_entity file via its identifier, a second layer of linking takes over: shortnames. A client entity file acts as a switchboard. It pulls together geometries, textures, materials, animations, and animation controllers—all of which live in separate files with long, cumbersome identifiers—and assigns each one a local alias called a shortname.
When you define a texture in RP/entity/my_mob.entity.json, you map a shortname key (like "default") to the actual file path (like "textures/entity/my_mob"). When you define a geometry, you map a shortname key (like "default") to the full geometry identifier found inside the model file (like "geometry.custom.my_mob").
The render_controllers and scripts sections at the bottom of the client entity file never touch file paths or full geometry identifiers. They only read the shortnames you established higher up in the same file, or inside an external render controller file. This indirection allows a single render controller to work across dozens of different mobs, provided all of those mobs use the same shortnames (such as "default") for their base assets.
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "craft:copper_golem",
"materials": {
"default": "entity_alphatest"
},
"textures": {
"default": "textures/entity/copper_golem/normal",
"oxidized": "textures/entity/copper_golem/oxidized"
},
"geometry": {
"default": "geometry.craft.copper_golem"
},
"animations": {
"walk": "animation.craft.copper_golem.walk",
"look": "animation.common.look_at_target"
},
"scripts": {
"animate": [
"look",
{
"walk": "query.modified_move_speed > 0.1"
}
]
},
"render_controllers": [
"controller.render.default"
]
}
}
}Texture Atlas Links for Blocks and Items
Unlike entities, which reference texture file paths directly inside their client definition files, blocks and items rely on intermediate texture atlas files. This is a common stumbling block for developers moving from entity creation to block creation. You cannot write a raw texture path inside a behavior block's minecraft:material_instances component or an item's minecraft:icon component.
Instead, you must register a texture shortname inside RP/textures/terrain_texture.json for blocks, or RP/textures/item_texture.json for items. Inside these atlas files, you define a custom string key under the texture_data object and point its textures property to the actual image path relative to the Resource Pack root, omitting the .png extension.
Once the atlas entry exists, you use that custom string key inside your Behavior Pack block or item file. This extra linking step exists so the game engine can stitch all block and item textures into massive single texture sheets in memory during startup, optimizing rendering performance.
| Asset Type | Where the Shortname is Defined | Where the Shortname is Used |
|---|---|---|
| Entity Texture | description.textures in RP/entity/*.json | Render controller textures array (e.g., Texture.default) |
| Block Texture | texture_data in RP/textures/terrain_texture.json | minecraft:material_instances in BP/blocks/*.json or RP/blocks.json |
| Item Icon | texture_data in RP/textures/item_texture.json | minecraft:icon component in BP/items/*.json |
| Sound Event | sound_definitions in RP/sounds/sound_definitions.json | RP/sounds.json, animation sound effects, or /playsound command |
{
"resource_pack_name": "craft_notes_rp",
"texture_name": "atlas.terrain",
"padding": 8,
"num_mip_levels": 4,
"texture_data": {
"craft_ basalt_bricks": {
"textures": "textures/blocks/basalt_bricks"
},
"craft_basalt_pillar_top": {
"textures": "textures/blocks/basalt_pillar_top"
}
}
}Linking Custom Geometry and Bones
Geometry linking works identically for both entities and custom blocks. Inside any file in the RP/models/entity/ or RP/models/blocks/ folders, the model's true name is defined in its description.identifier string, which must begin with the prefix geometry. followed by your custom name. The actual filename of the .geo.json file is completely ignored by the game engine.
For blocks using the modern Behavior Pack components, you link directly to this geometry identifier inside the minecraft:geometry component without needing a client entity switchboard. However, a secondary link happens inside the model itself: bone visibility and material mapping.
In a block model, the names of the cubes or groups do not control textures; instead, each cube specifies a material instance name in its face definitions (or inherits one). In your BP/blocks/ file, the keys inside minecraft:material_instances must match the material names exported by the geometry, or use the wildcard * to catch all unassigned faces. For entities, bone names act as the link target for animations. If an animation file tries to rotate a bone named head, and the geometry file calls that bone Head with a capital H, the animation link fails.
{
"format_version": "1.20.80",
"minecraft:block": {
"description": {
"identifier": "craft:basalt_pillar"
},
"components": {
"minecraft:geometry": "geometry.craft.pillar",
"minecraft:material_instances": {
"*": {
"texture": "craft_ basalt_bricks",
"render_method": "opaque"
},
"ends": {
"texture": "craft_basalt_pillar_top",
"render_method": "opaque"
}
},
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 2.0
}
}
}
}Connecting Script Custom Components
In modern Bedrock development, the old Holiday Creator Features event chains have been replaced by a direct link between JSON definitions and TypeScript/JavaScript execution: Custom Components. This link bridges static data files in BP/blocks/ or BP/items/ with live code running in BP/scripts/.
To establish this link, your script must listen to the worldInitialize (or startup in newer betas) before-event and register a custom component using a namespaced string identifier. You provide an object containing lifecycle callbacks like onPlayerInteract, onTick, or onStepOn.
Once registered in script, that exact namespaced string is placed inside the minecraft:custom_components array of a block or item JSON file. When the world loads, the engine validates that every string listed in the JSON array has a corresponding registration in the script bundle. If the script fails to register the name during initialization, the block or item will throw a content log error and lose its custom functionality.
import { world } from "@minecraft/server";
world.beforeEvents.worldInitialize.subscribe((eventData) => {
eventData.blockComponentRegistry.registerCustomComponent(
"craft:pillar_interact",
{
onPlayerInteract(e) {
const { block, player } = e;
if (!player) return;
player.sendMessage(`Touched pillar at ${block.location.x}, ${block.location.y}, ${block.location.z}`);
}
}
);
});Localization Links in .lang Files
The final link in any polished add-on is connecting internal identifiers to human-readable text in RP/texts/en_US.lang. Without this link, players will see raw translation keys like tile.craft:basalt_pillar.name or item.craft:copper_wrench.name in their inventory.
Unlike JSON files, .lang files use a strict key=value format per line with no spaces around the equals sign. The engine generates the expected key automatically from your Behavior Pack identifier based on the asset type. Knowing the exact prefix and suffix pattern the engine expects for each asset type is essential, as the game does not warn you when a translation key is missing—it simply prints the raw key to the screen.
| Asset Type | Generated Key Pattern | Notes |
|---|---|---|
| Entity Name | entity.<namespace>:<name>.name | Used in death messages and name tags. |
| Spawn Egg | item.spawn_egg.entity.<namespace>:<name>.name | Automatically looked up when spawn_egg is enabled. |
| Custom Block | tile.<namespace>:<name>.name | Uses the legacy 'tile.' prefix even on modern format versions. |
| Custom Item | item.<namespace>:<name> | Modern items often omit the '.name' suffix unless overridden by minecraft:display_name. |
pack.name=Craft Notes Add-On
pack.description=Reference patterns for linking pack assets.
entity.craft:copper_golem.name=Copper Golem
item.spawn_egg.entity.craft:copper_golem.name=Spawn Copper Golem
tile.craft:basalt_pillar.name=Basalt Pillar
item.craft:copper_wrench=Copper WrenchWhich versions this applies to
Applies to Minecraft Bedrock 1.20.80 and newer. Manifest linking, client entities, texture atlases, and .lang patterns are stable back to 1.16.0. Block and item Custom Components are stable in 1.21.0+ and do not require any experimental toggles when using current format versions.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| [Blocks][error]-craft:basalt_pillar | minecraft:material_instances - Texture 'textures/blocks/basalt_bricks' is not found in terrain_texture.json | You put a direct file path inside the block's material_instances component instead of the shortname defined in RP/textures/terrain_texture.json. | Open terrain_texture.json, create a shortname key under texture_data that points to your file path, and put only that shortname key into the block JSON. |
| Custom entity spawns in the world (casts a shadow, makes sounds, or can be hit) but its model and texture are completely invisible. | Either the description.identifier in BP/entities/ does not exactly match the description.identifier in RP/entity/, or the geometry shortname mapped in the client entity does not match the geometry.name string inside the .geo.json file. | Check both entity files for typos in the identifier, then verify that the string in RP/models/entity/ begins with 'geometry.' and matches what is written in the client_entity geometry object. |
| Failed to load manifest: Invalid UUID or missing dependency when importing or activating the pack. | You copied the same UUID into both the header and modules sections, or you put the Resource Pack's module UUID into the Behavior Pack's dependencies array instead of its header UUID. | Generate a brand new UUIDv4 for every single uuid field across all manifests, then copy only the Resource Pack's header.uuid into the Behavior Pack's dependencies block. |
| [Scripting][error]-Custom component 'craft:pillar_interact' is not registered. | The block JSON references a custom component name in minecraft:custom_components, but the script either did not run, crashed before reaching worldInitialize, or spelled the component name differently. | Verify that BP/manifest.json has a valid script module pointing to your entry file, check the Content Log for script syntax errors, and ensure registerCustomComponent runs inside worldInitialize. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
