The Core Worldgen Systems
Bedrock divides world generation into four distinct systems, each living in its own behavior pack folder. Rather than putting all placement logic into a single file, the game separates 'where' something generates from 'what' actually gets built in the world.
For the majority of add-on projects, you will spend most of your time pairing Feature Rules with Features. Features define the physical object—such as an ore vein, a single block, or a cluster of flowers—while Feature Rules define the biome conditions, frequency, and coordinate math required to place that feature inside a newly generated chunk.
| System | Behavior Pack Folder | What it does |
|---|---|---|
| Feature Rules | BP/feature_rules/ | Determines when, where, and how often a feature attempts to generate within a chunk based on biome tags and Molang math. |
| Features | BP/features/ | Defines the actual blocks to place or the logic to chain multiple features together (such as ore veins, single blocks, or scatter patterns). |
| Structures | BP/structures/ | Holds saved .mcstructure files exported from Structure Blocks, which can be placed into the world by Jigsaw systems or structure features. |
| Biomes | BP/biomes/ | Configures climate, surface blocks, terrain height parameters, and the biome tags that Feature Rules and entity spawn rules listen for. |
How Features and Feature Rules Connect
A feature will never appear in the world on its own unless another file calls its identifier. The entry point for all natural feature generation is a Feature Rule. When a new chunk generates, the engine evaluates all active Feature Rules whose biome filters match the current biome.
Once a Feature Rule's conditions pass, it calculates a target coordinate (X, Y, and Z) and triggers the specific feature listed in its description block. That feature might place blocks directly, like a `minecraft:ore_feature`, or it might be a proxy feature like `minecraft:aggregate_feature` or `minecraft:scatter_feature` that runs further coordinate offsets and calls yet another feature down the chain.
Generation Passes
World generation happens in a strict sequential order within every chunk, controlled by the `distribution_pass` property inside a Feature Rule. Choosing the correct pass is critical because a feature can only check or replace blocks that already exist at that point in the chunk's lifecycle.
For example, ores use `underground_pass` because the base stone of the terrain has already been carved out, whereas trees and flowers use `surface_pass` so they can accurately query the final heightmap of the grass or sand. There is also a `sky_pass` for floating elements, and `pregeneration_pass` or `first_pass` for heavy modifications that must happen before standard terrain decoration.
Minimal Example: Generating a Custom Ore
To put a custom block underground, you need two JSON files: one in `BP/features/` to define the ore cluster and what vanilla blocks it is allowed to overwrite, and one in `BP/feature_rules/` to scatter that cluster through Overworld stone.
First, create the `minecraft:ore_feature`. Note that the file name must match the identifier name (without the namespace) for the game to locate and link the feature reliably.
{
"format_version": "1.13.0",
"minecraft:ore_feature": {
"description": {
"identifier": "example:custom_copper_ore_feature"
},
"count": 8,
"replace_rules": [
{
"places_block": "minecraft:copper_block",
"may_replace": [
"minecraft:stone",
"minecraft:andesite",
"minecraft:diorite",
"minecraft:granite"
]
}
]
}
}Attaching the Feature Rule
Next, create the Feature Rule that references `example:custom_copper_ore_feature` in its `places_feature` field. This rule checks for the `overworld` biome tag during the `underground_pass`, and attempts to spawn the vein 10 times per chunk at a uniform random height between Y=0 and Y=64, across the full 16x16 horizontal span of the chunk.
{
"format_version": "1.13.0",
"minecraft:feature_rules": {
"description": {
"identifier": "example:overworld_custom_copper_ore",
"places_feature": "example:custom_copper_ore_feature"
},
"conditions": {
"placement_pass": "underground_pass",
"minecraft:biome_filter": [
{
"test": "has_biome_tag",
"operator": "==",
"value": "overworld"
}
]
},
"distribution": {
"iterations": 10,
"x": {
"distribution": "uniform",
"extent": [ 0, 16 ]
},
"y": {
"distribution": "uniform",
"extent": [ 0, 64 ]
},
"z": {
"distribution": "uniform",
"extent": [ 0, 16 ]
}
}
}
}Biome Tags and Filtering
You rarely target a single biome by its full identifier in a Feature Rule. Instead, `minecraft:biome_filter` uses `has_biome_tag` to query the tags assigned to biomes inside their `BP/biomes/` definition files. Tags like `overworld`, `monster`, `forest`, `cold`, and `ocean` allow your features to integrate automatically with both vanilla biomes and any custom biomes that share those tags.
You can combine multiple filter tests using `all_of` and `any_of` arrays inside `minecraft:biome_filter` to narrow down placement—for instance, requiring a biome to have the `forest` tag while excluding any biome with the `mutated` or `frozen` tags.
Which versions this applies to
Features and Feature Rules (`format_version` 1.13.0) are stable and work in base worlds since Bedrock 1.13.0 without any experimental toggles. Modifying vanilla biomes or adding custom biomes in `BP/biomes/` has historically required experimental toggles (such as Custom Biomes) depending on the exact engine version.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Content Log Error: [Features][error]-Feature [example:my_feature] does not match expected file name | Unlike blocks or entities, Bedrock's feature parser strictly requires the JSON filename (excluding the .json extension) to match the identifier string after the colon. | If your feature identifier is "example:custom_copper_ore_feature", ensure the file is named exactly "custom_copper_ore_feature.json" inside the BP/features/ folder or a subfolder. |
| The pack loads with zero Content Log errors, but the feature never generates in the world. | Either you are exploring chunks that were already generated before the pack was applied, or the feature's replacement/placement rules are failing against the blocks at the chosen Y level (for instance, using a heightmap query in `underground_pass` or setting Y extents below -64). | Test in a brand new world or teleport thousands of blocks into unvisited chunks. Temporarily raise `iterations` to 50 and set `places_block` to something unmistakable like `minecraft:glowstone` on the surface to verify your Feature Rule is firing. |
| Surface features like custom plants or single blocks generate floating in the air or buried underground. | The Feature Rule's `y` distribution is using fixed numbers instead of querying the terrain heightmap. | In your Feature Rule's `distribution` block, set `"y": "query.heightmap(variable.worldx, variable.worldz)"` so the feature evaluates right at the highest solid block of that X/Z column. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
