Structures & jigsaw

The full custom-structure pipeline: the structure definition, the structure set that places it, the template pool and processor list that assemble its pieces, and the .nbt builds plus the structure-block / jigsaw-block workflow behind them. Primary syntax targets 1.21+ (singular worldgen/structure/ and structure/ folders).

Structure definition

The structure definition JSON at data/<namespace>/worldgen/structure/ is distinct from the .nbt build files (see below). It declares which biomes the structure may generate in, its generation step, mob-spawn overrides, terrain adaptation, and — for jigsaw structures — the starting template pool and expansion limits.

worldgen/structure — jigsaw root schema shape

{
  "type": "minecraft:jigsaw",
  "biomes": "#minecraft:has_structure/village_plains",
  "step": "surface_structures",
  "terrain_adaptation": "beard_thin",
  "spawn_overrides": {},
  "start_pool": "example:village/town_centers",
  "size": 6,
  "start_height": { "absolute": 0 },
  "project_start_to_heightmap": "WORLD_SURFACE_WG",
  "max_distance_from_center": 80,
  "use_expansion_hack": false
}

Structure types

One data-driven type plus fifteen hardcoded types. Most custom work uses jigsaw.

KindTypes
Data-drivenjigsaw — villages, outposts, bastions, ancient cities, trail ruins, trial chambers, and all custom structures.
Hardcoded (15)buried_treasure, desert_pyramid, end_city, fortress, igloo, jungle_temple, mineshaft, nether_fossil, ocean_monument, ocean_ruin, ruined_portal, shipwreck, stronghold, swamp_hut, woodland_mansion

Hardcoded types accept the common fields below plus a few type-specific ones — e.g. mineshaft has mineshaft_type + probability; ocean_ruin has biome_temperature, large_probability, cluster_probability; ruined_portal has a setups[] list; shipwreck has is_beached; nether_fossil has height.

Common fields (all structure types)

FieldNotes
typeOne of the types above.
biomesA biome id, an inline array, or a #tag. Structure biome tags live in tags/worldgen/biome/.
stepGeneration step enum — surface_structures is typical. (Full enum: raw_generation, lakes, local_modifications, underground_structures, surface_structures, strongholds, underground_ores, underground_decoration, fluid_springs, vegetal_decoration, top_layer_modification.)
spawn_overridesPer-mob-category spawn rules (optional). See below.
terrain_adaptationnone, beard_thin, beard_box, bury, or encapsulate — what each one does to the terrain, and which vanilla structures use it, is in the terrain-adaptation section immediately below.

spawn_overrides is an object keyed by mob category (monster, creature, ambient, water_creature, water_ambient, misc, underground_water_creature, axolotls). Each entry sets a bounding_box (piece or full) and a spawns list:

spawn_overrides — one category

{
  "monster": {
    "bounding_box": "piece",
    "spawns": [ { "type": "minecraft:pillager", "weight": 1, "minCount": 1, "maxCount": 2 } ]
  }
}
🎩 Trick

An empty spawns list suppresses natural spawns inside the box — the Ancient City / Trial Chamber "no mobs wander in" trick.

terrain_adaptation decides how the world is reshaped to receive the structure. It is optional and defaults to none. It is a field of the base structure type, not of jigsaw — nether_fossil and stronghold use it without being jigsaw structures at all.

The four active values are two families of two. The beard family is for structures that sit on the surface and need ground beneath them; the bury family is for structures that sit inside rock and need to stay enclosed. Within each family, the second value is the heavier variant.

ValueWhat it does to the terrainVanilla structures
noneNothing. The structure is stamped into whatever terrain is there, including open air.23 (by omission) + bastion_remnant, which is the only jigsaw structure to leave it unset
beard_thinGenerates terrain under the structure to support it, and removes terrain inside it.25 — every village and zombie village, pillager outpost, all 18 abandoned camps, and nether_fossil
beard_boxThe heavier alternative to beard_thin.1 — ancient_city
buryGenerates terrain surrounding the structure so that it ends up buried.2 — stronghold and trail_ruins
encapsulateThe heavier alternative to bury.1 — trial_chambers

So of the 52 vanilla structure definitions, 29 declare the field and 23 rely on the none default. The distribution is lopsided on purpose: beard_thin is the ordinary case, and the other three values are each used by exactly the one or two structures whose shape demands them.

⚠ Version differences
  • 22w13a (1.19) — terrain_adaptation replaced the boolean adapt_noise, which defaulted to false. Before that the choice was binary: adapt the noise or do not.
  • A pre-1.19 pack carrying adapt_noise has no equivalent to beard_box, bury or encapsulate — those distinctions did not exist to express.

Two things follow from the fact that the adaptation reshapes terrain rather than the structure:

The adaptation is charged against the size budget. Exceed the remaining allowance and the pack does not fail to validate — it fails to load, with Horizontal structure size including terrain adaptation must not exceed 128, which is why a structure can pass every static check and still leave the game refusing the whole pack. The three blending adaptations cost 12, so the usable ceiling under them is 116.

Vanilla sits on that ceiling deliberately rather than near it. Reading terrain_adaptation beside max_distance_from_center across all 52 vanilla structure definitions, the only two that reach it are the two largest, and both stop at exactly 116:

Structureterrain_adaptationmax_distance_from_center
ancient_citybeard_box116
trial_chambersencapsulate116
villages, pillager outpost, abandoned campsbeard_thin80
trail_ruinsbury80
strongholdburyunset
📌 Documented, not measured

Whether bury carries the same 12-block cost cannot be settled from the game's data: no vanilla structure uses bury above 80, so nothing in the pack exercises the limit. The rule as documented draws no exception — the horizontal cap is stated as 1–128 when terrain_adaptation is none and 1–116 otherwise, which includes bury. That is a documented rule rather than a measured one, so the two sources are marked apart here rather than merged: treat 116 as the ceiling under bury, and know that vanilla provides no example either way.

encapsulate is the value most often misread here. It reads as “underground, different rules” while costing exactly what the obviously-terrain-shaping beard_box costs. A field report bears this out: a third-party pack was found with two structures sitting on the 116 ceiling rather than one — a surface keep under beard_box and its basement under encapsulate — because the basement was assumed to be uncharged. Every value except none is charged.

Jigsaw-specific fields

FieldType / values
start_poolTemplate pool id (must contain ≥ 1 element) — the first piece.
sizeInt, max jigsaw recursion depth. Capped at 20; villages use 6 and trial_chambers is the only vanilla structure at the cap. The lower bound is where the sources disagree — see the note below.
start_heightHeight provider (see projection behavior below).
project_start_to_heightmapHeightmap enum, or omit to disable surface projection.
max_distance_from_centerInt; horizontal Chebyshev cap. Hard max 128, and the terrain adaptation is charged against it: none allows 1–128, while beard_thin, beard_box and encapsulate cost 12, leaving 116 (see the note below).
use_expansion_hackBool — legacy vertical-expansion behavior.
pool_aliasesList — redirect pool names at generation time (see below).
dimension_padding{ "bottom": n, "top": n } — keep pieces away from world limits.
liquid_settingsapply_waterlogging (default) or ignore_waterlogging.

start_height × project_start_to_heightmap interaction:

pool_aliases entries take one of three shapes:

pool_aliases — direct, random, random_group

{ "type": "minecraft:direct", "alias": "example:target", "target": "example:pool_a" }

{ "type": "minecraft:random", "alias": "example:target",
  "targets": [ { "data": "example:pool_a", "weight": 1 }, { "data": "example:pool_b", "weight": 2 } ] }

{ "type": "minecraft:random_group",
  "groups": [ { "data": [ /* list of direct/random aliases */ ], "weight": 1 } ] }

data/example/worldgen/structure/tall_tower.json (single-piece surface structure)

{
  "type": "minecraft:jigsaw",
  "biomes": "#minecraft:has_structure/mineshaft",
  "step": "surface_structures",
  "terrain_adaptation": "beard_thin",
  "start_pool": "example:tall_tower",
  "size": 1,
  "start_height": { "absolute": 0 },
  "project_start_to_heightmap": "WORLD_SURFACE_WG",
  "max_distance_from_center": 80,
  "use_expansion_hack": false,
  "spawn_overrides": {}
}
⚠ Version differences
  • Since 1.19 (22w11a): the consolidated jigsaw type was introduced, merging the old village / pillager_outpost / bastion_remnant configured-structure-feature types; the registry moved from configured_structure_feature to structure.
  • Since 1.21 (pack_format 48): folder is singular worldgen/structure/; dimension_padding, liquid_settings, pool_aliases, and split horizontal/vertical distance handling are available.
  • size max: was 7 in ≤ 1.20.2, raised to 20 from 1.20.3+.
  • size minimum: the field reference gives the range as 0–20; the game's own codec is intRange(1, 20). Only the floor is in dispute and no vanilla structure sits on it — the shipped range is 2–20 — so treat 1 as the minimum.
  • Structure biome tags live in tags/worldgen/biome/ — not tags/biome/.
💡 Best practice
  • Single-build structure: size: 1, a start_pool with one single_pool_element, and a start piece containing one downward/heightmap-facing jigsaw — no other pieces needed.
  • Every jigsaw structure gets a random rotation even with aligned joints — design relative-coordinate mechanics (functions, puzzles) to be rotation-agnostic behind an anchor block.
  • Reduce max_distance_from_center / size if a sprawling structure hurts chunk-load performance.

Structure set

A structure set at data/<namespace>/worldgen/structure_set/ controls where and how often a group of structure definitions is attempted. Biome validity is not here — that lives on each structure. Vanilla groups same-frequency structures (all village biome variants) into one set.

worldgen/structure_set — root schema shape

{
  "structures": [ { "structure": "example:tall_tower", "weight": 1 } ],
  "placement": { "type": "minecraft:random_spread", "salt": 1234567, "spacing": 32, "separation": 8 }
}

Fields

structures[] — each { "structure": <id>, "weight": <positive int> }. On a successful attempt, one is chosen by weight among those whose biome/conditions pass.

placement common fields:

FieldNotes
typeminecraft:random_spread or minecraft:concentric_rings.
saltNon-negative int seed offset (unique per set).
frequencyFloat 0–1 (default 1) — attempt keep-chance.
frequency_reduction_methoddefault, legacy_type_1, legacy_type_2, legacy_type_3.
locate_offset[x,y,z] chunk offset, each −16..16.
exclusion_zone{ "other_set": <id>, "chunk_count": 1–16 } — fail if too near another set.

random_spread extras: spacing (0–4096 chunks, average grid cell), separation (0–4096, minimum gap, must be < spacing), spread_type (linear default, or triangular).

concentric_rings extras (used for strongholds): distance (0–1023, ring spacing in 6-chunk units), count (1–4095 total placements), spread (0–1023, placements on the nearest ring), preferred_biomes (biome ids / #tags).

data/example/worldgen/structure_set/towers.json

{
  "structures": [
    { "structure": "example:tall_tower", "weight": 3 },
    { "structure": "example:short_tower", "weight": 1 }
  ],
  "placement": {
    "type": "minecraft:random_spread",
    "salt": 84920155,
    "spacing": 34,
    "separation": 8,
    "spread_type": "linear",
    "frequency": 0.6,
    "exclusion_zone": { "other_set": "minecraft:villages", "chunk_count": 8 }
  }
}
⚠ Version differences
  • Structure sets were introduced with the 1.18 worldgen rewrite; the schema is stable through 1.21.8.
💡 Best practice
  • Give every custom set a unique large salt — reused salts make different structures land on identical chunks.
  • Lower spacing / separation = denser; always keep separation < spacing or generation errors.
  • exclusion_zone is how vanilla keeps pillager outposts away from villages.
  • /locate structure <id> searches by structure definition, independent of the set.

Template pool

A template pool at data/<namespace>/worldgen/template_pool/ is "a loot table for builds." A jigsaw's start_pool and each jigsaw block's Target Pool point at one of these. It weights the candidate pieces and supplies a fallback.

FieldNotes
fallbackAnother pool id used when elements can't fit or size is exhausted (an end-cap); minecraft:empty if none.
elements[]Each { "weight": <int 1–150>, "element": { ... } }.
nameOptional — ignored on load; the file path is the id.

element_type (inside each element):

TypeFields
single_pool_elementlocation (NBT id), processors (list id/inline), projection, optional override_liquid_settings.
legacy_single_pool_elementAs single; legacy structure-block block handling.
list_pool_elementelements[] (list of pool elements placed together), projection.
feature_pool_elementfeature (placed feature id/inline), projection.
empty_pool_element(nothing to place) — terminator.

projection — the two values, and what terrain_matching actually does

projection is required on every pool element except empty_pool_element, and takes exactly two values.

ValueBehaviourVanilla uses
rigidThe piece is stamped at the Y its jigsaw connection put it at and keeps its exact shape. Houses, towers, town centres — and always the start piece.2,073
terrain_matchingEach block's Y is adjusted to follow the terrain, so a flat piece drapes over a slope instead of cutting a shelf or bridging a dip.183

terrain_matching is not a placement mode of its own. It is a shorthand: the game hardcodes a minecraft:gravity processor onto the template when it sees this projection, with heightmap at its default WORLD_SURFACE_WG and offset at -1. Everything the projection does, that processor does. (Despite the name, this processor has nothing to do with falling-block physics; it rewrites Y coordinates as the template is stamped.)

Two consequences follow directly from those two parameter values:

📌 Note

That water case is not hypothetical — it is why village streets carry a processor list at all. minecraft:street_plains exists almost entirely to repair what the projection does at a waterline: a dirt_path block that lands where the world holds water becomes oak_planks, and the road's grass_block and dirt shoulders in the same situation are turned back into water. The plank bridges you see over village ponds are that rule, not a bridge piece. The remaining rule is cosmetic: 10% of path blocks revert to grass for wear.

What vanilla actually uses it for

Measured across every template pool in the game: of the 183 terrain_matching elements, 182 are village streets and street terminators — every biome variant and every zombie variant. The single remaining use is pillager_outpost/feature_plates, the ground plates scattered around an outpost's base. It is, in vanilla, a roads-and-groundplates feature and nothing else.

⚠ Gotcha

It deforms anything with vertical geometry. Because every column is dropped independently onto the heightmap, a piece with height does not slide down the slope — it shears. A wall built across a gradient arrives as a staircase of disconnected columns. Reserve it for pieces that are essentially one layer thick and meant to lie on the ground; make everything else rigid, and the start piece rigid without exception.

📌 Note

projection and the structure's terrain_adaptation pull in opposite directions and are easy to conflate. terrain_adaptation (beard_thin, beard_box, bury, encapsulate) reshapes the terrain to accommodate the structure; projection: terrain_matching reshapes the piece to accommodate the terrain. Villages use both at once — village_plains is beard_thin at the structure level while its streets are terrain_matching at the element level. Of the 28 vanilla jigsaw structures, 24 use beard_thin; ancient_city uses beard_box, trail_ruins bury, trial_chambers encapsulate, and bastion_remnant declares none.

🎩 Trick

Write the gravity processor yourself and you get the parameters the projection hides. Keep the element rigid and put a minecraft:gravity processor in its processor list, and you choose both the heightmap and the offset: offset: 0 to drape a piece on the surface instead of sunk into it, or heightmap: "OCEAN_FLOOR_WG" to follow the seabed under water rather than the water surface — which is exactly the case the vanilla street rules have to patch up after the fact. Vanilla writes zero gravity processors by hand; all 52 processor entries in its processor lists are rule, protected_blocks, block_rot and capped. Every gravity processor in the game is one terrain_matching injected.

data/example/worldgen/processor_list/drape_on_seabed.json

{
  "processors": [
    { "processor_type": "minecraft:gravity",
      "heightmap": "OCEAN_FLOOR_WG",
      "offset": 0 }
  ]
}

Using beard_thin and terrain_matching together

The natural assumption is that a structure with beard_thin gets flattened ground, and that terrain_matching pieces then settle onto it so walls and floors line up. Neither half of that holds, and vanilla's own data says so.

beard_thin supports; it does not level. It generates terrain under the pieces and clears terrain inside them. The structure itself is anchored at a single point — 25 of 28 vanilla jigsaw structures set project_start_to_heightmap, which drops the start piece to the surface — and every rigid piece after that hangs off that anchor rather than off its own local ground. On a slope, distant pieces end up above the terrain and the beard fills the gap beneath them. The column of dirt and stone under a hillside village house is not a bug; it is beard_thin doing exactly its job.

📌 Note

Vanilla's data states the expectation directly. 24 of the 28 jigsaw structures set use_expansion_hack, documented as raising the structure's vertical limit “to prevent cut-off when terrain height varies” — and they are precisely the 24 that combine beard_thin with a heightmap-projected start. The four that leave it off (ancient_city, bastion_remnant, trial_chambers, trail_ruins) are the fixed-height and buried ones. A bearded surface structure is built on the assumption that the ground varies, not that it has been flattened.

Whether a wall matches its floor depends on where the boundary falls. Within a single piece there is one projection for the whole template: under rigid the wall and floor keep their exact relationship and always agree; under terrain_matching every column is dropped onto the heightmap independently, so the floor drapes while the wall shears into disconnected columns. Between two pieces it is worse, because the two Y values come from different computations entirely — a rigid piece takes its Y from the jigsaw connection, a terrain_matching piece from heightmap plus the injected offset: -1. They agree only by coincidence, and the sink guarantees a systematic one-block step even when the ground is level.

💡 Best practice

Keep a projection uniform along a whole jigsaw chain, and vanilla does exactly that. Across all 244 vanilla template pools that declare a projection, zero mix the two: 226 are uniformly rigid and 18 uniformly terrain_matching. Every terrain_matching pool falls back either to another terrain_matching pool or to minecraft:empty, so a road chain never degrades into a rigid end-cap. The projection changes only at the road-to-building boundary — which is where the step you see in game actually is.

⚠ Gotcha

So: put anything with height on rigid, and put a wall and the floor it meets in the same piece so one projection governs both. Use terrain_matching only for pieces that are essentially one layer thick, in pools that are uniformly terrain_matching with fallbacks to match. If you genuinely need flat ground, no adaptation will give it to you — beard_thin will happily support a building on a pillar rather than level the hill. Flatten it yourself with a generous rigid floor slab, or accept the pillar the way vanilla does.

Heightmaps a gravity processor can target

HeightmapHighest block of…Note
WORLD_SURFACE_WGany non-air blockThe default, and what terrain_matching uses. Worldgen only; falls back to WORLD_SURFACE elsewhere.
WORLD_SURFACEany non-air blockCounts water and vegetation.
OCEAN_FLOOR_WGblocks whose material blocks motionIgnores water — the seabed. Worldgen only.
OCEAN_FLOORblocks whose material blocks motionServer-side.
MOTION_BLOCKINGmotion-blocking blocks, plus fluids and waterlogged blocks
MOTION_BLOCKING_NO_LEAVESas above, excluding leavesServer-side.
📌 Note

The _WG suffix marks when the heightmap is used, not what it measures — both variants store the same quantity. What keeps a terrain_matching road off the treetops is generation order rather than the heightmap definition: structures are placed before most vegetation features run, so the surface it samples is still bare terrain.

data/example/worldgen/template_pool/tall_tower.json

{
  "name": "example:tall_tower",
  "fallback": "minecraft:empty",
  "elements": [
    { "weight": 3, "element": {
        "element_type": "minecraft:single_pool_element",
        "location": "example:tower/body",
        "processors": "example:mossify",
        "projection": "rigid" } },
    { "weight": 1, "element": {
        "element_type": "minecraft:single_pool_element",
        "location": "example:tower/body_ruined",
        "processors": "minecraft:empty",
        "projection": "rigid" } }
  ]
}
⚠ Version differences
  • list_pool_element, legacy_single_pool_element, and override_liquid_settings are Java-only; the schema is stable across 1.19 → 1.21.8.
💡 Best practice

Provide a small fallback pool (a blank wall / cap) so truncated jigsaw chains don't look sheared off. Use terrain_matching only for pieces meant to melt into the ground (roads); everything else rigid, especially the start piece.

Processor list

A processor list at data/<namespace>/worldgen/processor_list/ is an ordered set of block transformations applied as a template piece is stamped into the world (weathering, block swaps, gravity settling, loot injection). Pool elements reference it via processors. The root is a processors array-holder object:

worldgen/processor_list — root schema shape

{ "processors": [ { "processor_type": "minecraft:block_ignore", "blocks": [ { "Name": "minecraft:structure_void" } ] } ] }

Processor types

processor_typeFields
block_ignoreblocks[] (block states to skip placing).
block_rotintegrity (0–1 keep-fraction), optional rottable_blocks (block list).
gravityheightmap, offset (int) — settle each column to a heightmap.
jigsaw_replacementnone — convert leftover jigsaw blocks to air.
rulerules[] (see below).
block_agemossiness (float) — random weathering / cobweb / cracks.
blackstone_replacenone — swap cobblestone-family → blackstone-family.
lava_submerged_blocknone — waterlogged / air-in-lava handling.
protected_blocksvalue (block #tag that must never be overwritten).
cappeddelegate (a processor), limit (int provider — max applications).
nopnone.

Rules & rule tests

Each entry in a rule processor's rules[]: input_predicate tests the block in the template; location_predicate tests the block already in the world at that spot; if both pass, output_state is written.

rule — one entry

{
  "input_predicate":       { "predicate_type": "minecraft:always_true" },
  "location_predicate":    { "predicate_type": "minecraft:block_match", "block": "minecraft:red_sand" },
  "position_predicate":    { "predicate_type": "minecraft:always_true" },
  "output_state":          { "Name": "minecraft:red_sandstone" },
  "block_entity_modifier": { "type": "minecraft:passthrough" }
}

Rule tests (predicate_type — shared with ore targets):

Predicate typeFields
always_true—
block_matchblock
blockstate_matchblock_state
tag_matchtag (no leading #)
random_block_matchblock, probability
random_blockstate_matchblock_state, probability

Joint type only means something on a vertical joint

Minecraft only ever applies a Y rotation to a template piece. That single fact decides where joint matters:

The type is read from the parent connector — the end doing the placing — so that is where the fix goes.

⚠ The structure block GUI overrides the default

When the tag is absent, vanilla defaults the joint type from the front axis: horizontal defaults to aligned, vertical defaults to rollable. But the structure block GUI writes joint:"rollable" explicitly on everything it saves. So every vertical connector saved from the GUI is rollable whether you intended it or not, and pieces that should line up come out facing different directions — in the reported case, a gate roof that landed differently on all four gates. Set joint="aligned" on the parent connector.

💡 Do not let a validator flag this

Because the type is inert on a horizontal joint, setting aligned there is harmless and not a defect. A check that flags it is a false positive — one written in production fired on 31 groups of pieces that had always worked correctly. The narrow case worth diagnosing is the opposite: a vertical connector, rollable or absent, whose pool contains a piece that is not square in plan. That piece lands turned, and may not fit.

📌 How this was established

The two joint values and the field being optional are confirmed against vanilla-mcdoc @ eea955a6afdb. The axis-dependent default and the GUI behaviour are observed in production by a sibling pack, not stated in the schema — they are reported here as measured behaviour rather than specification.

Trial spawners and vaults: the block entities a structure has to configure

Both arrived with the trial chambers and both are inert until configured, so a structure that places one must also carry its NBT — either baked into the saved piece, which is how vanilla does it (see trial chambers as a system), or, when a processor rule is what places the block, through append_static.

minecraft:trial_spawner

Two independent behaviour blocks: normal_config and ominous_config (the latter used when the player carries Bad Omen). Since 1.21.2 each accepts either an inline config object or a string id referencing a data-driven trial_spawner file. That indirection pays off when the same config is needed in more than one processor list or structure; it saves nothing when several pool elements already share a single processor list, because that is one copy either way.

FieldMeaning
spawn_rangeHow far from the spawner a mob may appear (1–128).
total_mobs / total_mobs_added_per_playerMobs per activation, and how much each extra player adds.
simultaneous_mobs / simultaneous_mobs_added_per_playerHow many may be alive at once, and the per-player increment.
ticks_between_spawnDelay between spawns.
spawn_potentialsThe weighted list of what to spawn — see the casing note below.
loot_tables_to_ejectWeighted list of reward tables; one is chosen, then used once per nearby player.
items_to_drop_when_ominousTable used for the ominous item spawners during the fight.

minecraft:vault

Three parents, not two, and the split tells you which parts are yours to write:

FieldMeaning
config — authored
key_itemThe item stack required to open it.
loot_tableReward table. Defaults to minecraft:chests/trial_chambers/reward.
override_loot_table_to_displayWhich table generates the preview item. Defaults to whatever loot_table is — see the refresh loop below.
activation_range / deactivation_rangePlayer distances at which it wakes and sleeps.
server_data — runtime
state_updating_resumes_atTick at which the loot table is rolled again to refresh the display item.
rewarded_playersUUIDs that have already claimed it — a listed player can no longer open this vault, while others still can.
items_to_ejectThe queue still to be spat out. Each item is removed as it is ejected, and is previewed as the display item first.
total_ejections_neededHow many the roll started with. Does not change as items leave.
shared_data — synced to clients in range
display_itemThe item every nearby player sees floating in the vault.
connected_playersUUIDs currently in range.
connected_particles_rangeDistance over which the connecting particles are drawn.
📌 The preview is a loop, not a setting

Three fields across all three parents are one mechanism, which is why reading any of them alone is misleading:

  1. config.override_loot_table_to_display chooses which table generates the preview;
  2. shared_data.display_item holds the item that roll produced, and is what players actually see;
  3. server_data.state_updating_resumes_at is the tick that rolls it again.

So override_loot_table_to_display is not a cosmetic override bolted onto the side — it is one leg of a refresh loop that keeps the floating item changing while the vault waits.

⚠ SpawnPotentials and spawn_potentials are different block entities

Same content, two casings, and which one is correct depends on the block entity — not on the version:

  • minecraft:mob_spawner (the ordinary spawner) uses CamelCase: SpawnPotentials, SpawnData, SpawnCount, MaxNearbyEntities. Still current.
  • minecraft:trial_spawner uses snake_case inside its config: spawn_potentials.

And there is a third layer inside the second. The entity compound nested in a snake_case spawn entry is entity NBT, so it is CamelCase again. One valid rule therefore nests CamelCase inside snake_case inside CamelCase:

"block_entity_modifier": {
  "type": "minecraft:append_static",
  "data": {
    "normal_config": {                        // snake_case - block entity
      "spawn_potentials": [ {
        "weight": 1,
        "data": {
          "entity": {                         // snake_case key ...
            "id": "minecraft:zombie",
            "CustomName": { "text": "Guard" } // ... CamelCase value: entity NBT
          }
        }
      } ]
    }
  }
}

This is the shape most likely to be “fixed” by someone normalising the casing, and normalising it breaks it. Nothing here is inconsistent by accident: each key belongs to whichever system owns that level.

What did go stale is the entry shape, not the key. Up to 1.18 an entry was { Entity, Weight }; since 1.18 it is a weighted entry whose payload carries entity, custom_spawn_rules and — since 1.20.5 — equipment, which rolls a loot table onto the spawned mob. Anything still documenting Entity/Weight predates 1.18.

The eleven processor types, and which you can actually write

The worldgen/structure_processor registry holds eleven entries at 26.2. Only some are things you would ever put in a processor list by hand:

ProcessorTakes fields?In practice
ruleyesThe workhorse. 35 of vanilla’s 52 processor entries.
block_rotyesRandomly removes blocks. 6 entries.
protected_blocksyesRefuses to overwrite a block tag. 7 entries.
cappedyesCaps how many times a delegate applies. 4 entries.
block_age, block_ignoreyesWeathering and ignore-lists. Unused by vanilla.
gravityyesInjected by terrain_matching. Vanilla writes zero by hand — but writing one by hand works, and is how you get at the heightmap and offset.
jigsaw_replacementnoInjected. Substitutes each jigsaw block with its final_state. Registered, so it can be declared — see below.
blackstone_replace, lava_submerged_blocknoFixed behaviour, used by bastions and ruined portals.
nopnoDoes nothing. Useful as a placeholder delegate.

Counts measured from vanilla’s own processor lists at 26.2; the registry contents from the game’s shipped registry dump.

⚠ Processor ordering is not documented — including against jigsaw_replacement

A question with no published answer, recorded here as an open question rather than guessed at, because guessing is how the wrong thing gets repeated:

When a pool element’s own processors run, has jigsaw_replacement already substituted the jigsaw blocks with their final_state, or do the processors still see minecraft:jigsaw?

The Minecraft Wiki says only that jigsaw blocks are replaced “additionally” via the minecraft:jigsaw_replacement processor — it never states the order. Its Processor list page does not state ordering for any processor. The mcdoc schema models fields, not application order. So the honest answer today is that it is unknown, not that it is unimportant: it decides whether a rule processor can key off a colour that exists only as a final_state.

💡 You can declare the order instead of discovering it

minecraft:jigsaw_replacement is a registered processor type, so nothing stops you putting it in your own list at a position you choose — which makes the ordering yours rather than implicit:

"processors": [
  { "processor_type": "minecraft:jigsaw_replacement" },
  { "processor_type": "minecraft:rule", "rules": [ ... ] }
]

The configuration currently under test in a sibling pack

"processors": [
  { "processor_type": "minecraft:jigsaw_replacement" },
  { "processor_type": "minecraft:rule", "rules": [ ... ] }
]

Applied to 1×1×1 elements whose single cell is a jigsaw with final_state set to a coloured concrete powder, with rules matching those colours. If the decorations appear, declaring the processor first works; if bare powder appears, it does not.

📌 Two questions, deliberately kept apart

That test answers “does declaring jigsaw_replacement first work?” and nothing else. It cannot tell you the implicit order, because a powder result would only mean the lever did not help — not whether replacement normally runs before or after. Pinning the default still needs the discriminating test below, run with no jigsaw_replacement declared. Both are open; neither answer is assumed from the other.

Untested, and flagged as such. Vanilla declares jigsaw_replacement zero times — exactly the pattern gravity follows, and a hand-written gravity processor does work. That makes this principled rather than established. To settle it, place a 1×1×1 element whose one cell is a jigsaw with final_state: yellow_concrete_powder and a single rule matching minecraft:jigsaw to some obvious output: a diamond block means processors run first and their output survives, the powder means replacement wins, and a leftover jigsaw means the predicate cannot match it at all.

Position predicates (the optional position_predicate): always_true, linear_pos (min_chance, max_chance, min_dist, max_dist), axis_aligned_linear_pos (adds axis).

block_entity_modifier types — there are exactly four: clear, passthrough (keep existing NBT), append_static (data compound merged in), append_loot (loot_table — sets a LootTable so the container fills on first open).

💡 append_static is the only way to configure a block entity a processor creates

A rule processor can place a block, but placing a trial spawner or a vault is only half the job — both are inert without configuration. append_static is the route: it merges a data compound into the block entity the rule just created. Nothing else in the processor system can reach that NBT.

data/example/worldgen/processor_list/mossify.json

{
  "processors": [
    { "processor_type": "minecraft:block_age", "mossiness": 0.2 },
    { "processor_type": "minecraft:rule",
      "rules": [
        { "input_predicate": { "predicate_type": "minecraft:block_match", "block": "minecraft:sand" },
          "location_predicate": { "predicate_type": "minecraft:block_match", "block": "minecraft:red_sand" },
          "output_state": { "Name": "minecraft:red_sand" } }
      ] }
  ]
}
⚠ Version differences
  • block_rot, block_age, gravity, blackstone_replace, lava_submerged_block, and nop are Java-only; the set is stable across 1.18 → 1.21.8.
  • capped (delegate + limit int provider) and protected_blocks are present in 1.21.
🎩 Trick
  • append_loot in a rule's block_entity_modifier is how structures ship unfilled chests.
  • protected_blocks with a custom tag prevents a processor or terrain from overwriting hand-placed key blocks.
  • Wrap a heavy processor in capped to bound how many blocks it touches per piece.

Structure NBT & jigsaw workflow

Keep the two "structure" things straight:

Structure Block workflow

  1. /give @s structure_block. The placed block defaults to Load mode; click Load twice to reach Save mode.
  2. Set a Structure Name (namespace:path, lowercase / no spaces), a Relative Position (e.g. 1 0 1), and a Structure Size (max 48×48×48; extends in +X/+Y/+Z).
  3. Toggle Include Entities if display entities / mobs are inside (they must sit ≥ 0.01 blocks inside the box or they're dropped). Redstone state is not saved.
  4. Press SAVE — vanilla writes the file to the world's generated/<namespace>/structures/ (plural). Copy it into the data pack at data/<namespace>/structure/ (singular). The two folders are named differently; see below.
  5. Use Structure Void (/give @s structure_void) at the build's edges so natural terrain / foliage shows through instead of air. (Build with sponge as a placeholder, swap to voids last — voids have a tiny hitbox.)
⚠ Gotcha

The 1.21 rename applied to data pack folders only. The release note for pack_format 48 puts it exactly that way: it renamed “legacy folders like loot_tables and tags/items to loot_table and tags/item and functions to function”. Nothing else was named. Note what that does and does not tell you: the release note is silent on the world-save folder, which is an absence of a rename rather than a statement that it stayed plural. Resource packs still use models/, atlases/, blockstates/ and items/ (measured from the shipped assets). Assuming the singular rule applies everywhere sends you to a folder that does not exist, and nothing reports it: you simply find no files. Applying the singular rule everywhere sends you looking in a folder that does not exist, and nothing reports it: you simply find no files.

📌 Note

The plural here is documentation-only, and unverified. No shipped game artefact in this reference records it: generated/ is written at runtime by a structure block, not distributed with the game, so it is outside what can be audited here. Two Minecraft Wiki articles state the plural and the 1.21 release note does not contradict them, but silence is not confirmation and this page has not verified it against a vanilla-created save. Treat it accordingly.

A live server may not match either. generated/ is a world-save folder that anyone can reorganise by hand, and on a long-lived server they often have. A tree carrying near-duplicate namespaces, or .nbt files sitting at the namespace root with no subfolder at all, has been curated rather than written by the game, and tells you nothing about vanilla behaviour. The plural above is what vanilla writes; on a real server, list the directory rather than assume it.

Jigsaw blocks

/give @s jigsaw. Directional; the sideways interface shows:

FieldMeaning
Target PoolTemplate pool this jigsaw may spawn from (minecraft:empty if it's only a connector).
NameThis jigsaw's own name (what a parent's Target Name matches).
Target NameThe Name of the jigsaw in the child piece it should line up with.
Turns Into (final_state)Block that replaces the jigsaw after generation (usually minecraft:air; accepts blockstates).
Joint Type (up/down only)rollable (random rotation allowed) or aligned (fixed to the indicator line). Only meaningful on a vertical front — see below.
Selection / Placement Priority 1.20.4+Higher = generated first, so key children claim space before others.

Convention: pair a parent and child by swapping Name ↔ Target Name and namespacing both.

💡 Best practice

Single-piece structure recipe: a .nbt build (with one downward or heightmap-facing jigsaw, Target Pool minecraft:empty) → a one-element template_pool (single_pool_element, location = the .nbt, projection: rigid) → a structure definition with size: 1 pointing start_pool at that pool → a structure_set with a random_spread placement. Done.

🚧 Workaround
  • Structure Block max is 48³; larger builds must be split across jigsaw-connected pieces.
  • A jigsaw child's bounding box cannot intersect any previously placed piece's box — put jigsaws at the very edge facing out, or keep an internally-spawned child small enough to fit.
  • Max horizontal reach from the start center is 128 blocks (also bounded by max_distance_from_center and terrain adaptation).
  • Max jigsaw steps = 20 (7 in ≤ 1.20.2); villages use ~6. Deep branching hurts chunk-load performance. Everything inherits the start piece's random rotation.
⚠ Version differences
  • Since 1.21: the build folder is singular structure/. Structure Blocks in ≤ 1.20 saved to structures/, so rename after exporting a pre-1.21 build.
  • Since 1.20.4: jigsaw blocks expose Selection / Placement Priority.
  • Since 26.3 Snapshot 7: the block-state rename reaches saved templates too, not just JSON. Every block state in a .nbt palette now uses id/properties. Measured across all 1,511 vanilla 26.3 templates: 24,242 id keys and 17,770 properties, with zero Name or Properties remaining, and all 4,272 jigsaw blocks carrying orientation under properties. A tool that reads only the old keys sees every 26.3 jigsaw as unrotated rather than failing loudly. Note this is the block state key, unrelated to a jigsaw’s own Name field above. Existing templates keep working: a pack whose 320 templates all carry the pre-rename keys was observed generating normally in a 26.3 world, so unlike the JSON rename — which stops a file loading outright — this one breaks tools that read templates, not the packs themselves.
  • Multi-palette templates: a template may carry palettes — a list of alternative palettes — instead of a single palette. Twenty vanilla 26.3 templates do, all of them shipwrecks. A reader that looks only at palette finds no blocks at all in those, and reports an empty structure rather than an error.

Building and testing a jigsaw structure

The sections above describe each field. This one is the order of work, and the handful of rules that decide whether a jigsaw structure comes out the same every time or wanders somewhere new on every seed. The rules below come from tracing the jigsaw blocks of shipping packs rather than from reading the schema, and the numbers are measured results.

The rule that reframes everything: jigsaw cannot close a loop

A new piece only ever attaches to a free connector. Two pieces that are already placed never join each other. So the generator can never “close” a ring — a curtain wall, a cloister, a moat. What you do instead is make the geometry close and let the last connector go unfilled: walk the chain all the way round, and the final placement lands on the footprint of the start piece, is rejected as overlapping, and the connector is simply never used. The wall is continuous anyway, because the piece it wanted to place is already standing there. The ring closes by arithmetic, not by joining.

Vanilla itself ships connectors that can never fire

A jigsaw names a target, and the piece drawn from its pool must contain a jigsaw whose Name matches it. When nothing in that pool answers, the connector simply never fires: no error, no log line, nothing in game. This is worth knowing because vanilla does it too. Measured across all 1,511 vanilla 26.3 templates and 245 template pools, resolving each pool through its elements and its fallback chain:

Jigsaw connectors in vanilla 26.3Count
Total jigsaw blocks4,272
Pool is minecraft:empty — a deliberate terminator825
Target answered by the pool or its fallback3,184
Pointing at a feature pool — inert by construction234
Pointing at an alias placeholder — no file by design4
Target answered by nothing at all25

The middle two rows are the ones that trip up a validator, and neither is a fault. A feature_pool_element places a configured feature — a tree, usually — and a feature contains no jigsaw blocks, so its target can never be answered by anything and is simply inert; 24 of the 245 vanilla pools are feature-only. The four placeholders are the trial-chamber spawner pools (contents/melee, ranged, slow_ranged, small_melee), which have no files because pool_aliases supply them at generation time. Whether a checker trips over the 234 depends on how it walks a pool: one that follows only elements carrying a location skips feature elements naturally, while one that resolves every referenced pool id has to exclude them deliberately — the first sweep behind this table did not, and reported 259.

The remaining 25 are genuinely unanswered: 17 in trail_ruins, 7 in bastion_remnant, 1 in trial_chambers. Fifteen of them target hall from trail_ruins/buildings/grouped, a pool whose twenty pieces between them offer only room and empty.

⚠ Why this matters for your own pack

A misspelt target produces exactly the same silence as vanilla’s own dead connectors — the wing just is not there, and nothing tells you why. When a structure generates short, check whether the target name is answered by any piece in the pool it points at, rather than re-reading the piece that failed to appear. And if you write a validator, do not fail a build on a dead connector alone: vanilla would fail it 25 times. The signal worth acting on is a target that nearly matches a name in its pool, which is a typo; a target that matches nothing anywhere is usually deliberate. That rule was tested against this data: none of vanilla’s 25 dead targets is within one or two edits of a name its pool offers, so a near-miss check catches a misspelling while leaving vanilla clean.

Four rules that make a layout deterministic

  1. One pool per step, one element per pool. A wall built from a single wall_segments pool drawn from repeatedly is not a perimeter; it is a random walk of unpredictable length, and it wanders off one side. Number the chain instead — segment_1, segment_2, … — each its own pool with exactly one element.
  2. One connector name per join, used once on each side. A parent’s target names the child’s entry connector, and that connector carries exactly that name. Mirrored pairs such as a2b and b2a never connect.
  3. Cap every face you do not want to grow. Give it a jigsaw targeting minecraft:empty in pool minecraft:empty. Any face left with a live connector is a face that will sprout.
  4. Corners are in the chain, and they turn. The corner piece has its entry connector on one face and its exit on the perpendicular one. It ends one arm and starts the next; it is not decoration placed afterwards.
💡 What the rules bought, measured

Applied to a castle curtain wall that had been sprawling: reducing eight connectors per wall segment to one per face took the piece count from 856 to 195; breaking a gate–spacer recursion took it to 79; numbering the chain and capping every face settled it at 151 pieces with the minimum, mean and maximum identical across eight seeds. Identical across seeds is the number to aim for — it means the layout is no longer a random walk.

⚠ Pick even gate widths

Symmetry is arithmetic too. With a 9-wide gate and 6-wide corner turrets, the arm length works out to (31 + 2L) / 2 for a standoff L — an odd numerator, so the two arms can never be equal at any standoff and opposing arms miss the corner by one block. A 10-wide gate makes it exact.

The order of work

  1. Draw the chain before building pieces. Every join as parent connector → child pool → child entry connector. If you cannot write it as a list, the generator cannot follow it either.
  2. Build and save the pieces with structure blocks, placing jigsaw blocks on the faces from the chain — see Structure NBT & jigsaw workflow. Check the joint type on every vertical connector: the structure block GUI saves them as rollable whether you meant it or not (joint type).
  3. Write one pool per step, then the structure and its structure set. Choose projection per element, not per pool.
  4. Simulate before you generate. Drop the pack into MCJIG and look at the boxes. A pool that sprawls, recurses or never places the piece you care about is obvious in seconds, and it costs no world.
  5. Place it with /place structure in a flat test world, several times at different coordinates. A deterministic layout looks the same every time; if it does not, go back to rule 1.
  6. Read the log after every /reload. A pool or processor list that fails to parse is never registered, and the structure silently places nothing — see silent failures.

Trial chambers as a system

Trial chambers are the most complete worked example in vanilla of a structure that has to ship configured block entities, randomised encounters and a reward loop together. Everything below is measured from the game's own data at 26.3.

The parts

PartCountWhere
Structure1worldgen/structure/trial_chambers — jigsaw, start pool trial_chambers/chamber/end, size 20, starting between Y −40 and −20
Template pools47worldgen/template_pool/trial_chambers/…
Spawner configs28trial_spawner/trial_chamber/… — 14 spawner types, each with a normal and an ominous file
Loot tables19chest, spawner-eject and equipment tables
Processor lists1trial_chambers_copper_bulb_degradation — and it configures nothing

Configuration lives in the pieces, not in processors

The only trial-chamber processor list ages copper bulbs. Spawners and vaults arrive configured because their block-entity data is saved into the structure pieces themselves — and the two block entities do it differently:

A spawner piece — references, not a copy
{
  "id": "minecraft:trial_spawner",
  "normal_config":  "minecraft:trial_chamber/melee/zombie/normal",
  "ominous_config": "minecraft:trial_chamber/melee/zombie/ominous"
}
A vault piece — its config written inline
{
  "id": "minecraft:vault",
  "config": {
    "key_item":   { "id": "minecraft:trial_key", "count": 1 },
    "loot_table": "minecraft:chests/trial_chambers/reward"
  }
}

The spawner style suits behaviour that is reused: each piece names a config file instead of carrying a copy, so changing how a spawner behaves is one file rather than every piece that places it. The vault needs only two fields, so inline costs nothing. The ominous vault is the same piece shape with minecraft:ominous_trial_key and chests/trial_chambers/reward_ominous. For every field these blocks accept, see trial spawners and vaults.

How a chamber decides its mobs

Spawner pieces do not pick a mob. They pull from placeholder pools such as trial_chambers/spawner/contents/melee, and the structure's pool_aliases resolve those placeholders once per generated structure:

PlaceholderAlias typeResolves to one of
contents/ranged and contents/slow_rangedrandom_groupskeleton, stray or poison skeleton — the same one for both
contents/meleerandomzombie, husk or spider
contents/small_meleerandomslime, cave spider, silverfish or baby zombie

That is why a chamber feels themed rather than shuffled: every ranged and slow-ranged spawner in one structure agrees on the skeleton variant, because a random_group picks a whole group of aliases together, while melee and small melee each roll on their own. Breeze spawners have no alias and are always breezes. The three alias shapes are described under jigsaw-specific fields; this is the clearest reason to use the group form — whenever two placeholders must stay consistent with each other.

That timing is measured in a generated world, not inferred from the files. Across 23 chambers generated in a 26.3 world, all 76 alias resolutions were internally consistent: not one chamber mixed two mobs for the same placeholder, and every chamber resolved its random_group exactly once, so ranged and slow_ranged always agreed with each other. melee and small_melee varied between chambers but never within one. Two honest limits on that: it is 23 chambers from a single world, and it observes the outcome rather than the engine’s code path.

What “ominous” changes

Measured across all 14 spawner types, the ominous config differs from the normal one in these fields:

FieldDiffers inWhat changes
loot_tables_to_eject14 of 14No normal config names its eject tables, so it takes the default. Every ominous config names them explicitly — for the zombie spawner, an ominous key at weight 3 and consumables at weight 7.
spawn_potentials9 of 14The ominous mob also rolls equipment — for the melee zombie, from equipment/trial_chamber_melee with a drop chance of 0, so it is armed but drops nothing.
total_mobs, simultaneous_mobs5 of 14 Breeze, spider, cave spider, silverfish and slime change how many mobs spawn instead.

The last two rows do not overlap: each ominous spawner either arms its mobs or changes their numbers, never both.

The reward loop

  1. A cleared spawner ejects loot. Ominous configs name their tables, an ominous key among them. Normal configs leave loot_tables_to_eject to its default, which these files do not state; the vault expects a trial_key, and spawners/trial_chamber/key is the table that produces one.
  2. The key opens a vault whose key_item matches it.
  3. The vault rolls chests/trial_chambers/reward, which is itself a table of tables: one roll of reward_rare (weight 8) or reward_common (weight 2); one to three rolls of reward_common; and a 25% chance of reward_unique.
💡 Copying the pattern

For your own dungeon: put spawner behaviour in named trial_spawner files and reference them from the saved pieces; keep a vault's two fields inline; resolve encounter variety with pool_aliases rather than by duplicating rooms; and use a random_group for anything that has to match across the structure. Match each vault's key_item to the key a spawner actually ejects — a mismatched key is a vault nobody can open, and nothing will tell you.

The /place command

Used for testing structure generation without waiting for natural spawns.

/place — the four subcommands

/place feature   <configured_feature_id> [<pos>]
/place structure <structure_id>          [<pos>]
/place jigsaw    <start_pool> <target_jigsaw_name> <max_depth> [<pos>]
/place template  <template_id> [<pos>] [<rotation>] [<mirror>] [<integrity>] [<seed>] [strict]

What each argument resolves against

The four subcommands look alike and point at three different places. This is the distinction that most often produces a confident, wrong "does not exist" — the word structure names two unrelated things that legitimately coexist in one pack.

SubcommandArgument parserResolves against
place featureminecraft:featuredata/<ns>/worldgen/configured_feature/
place jigsawminecraft:resource_key, registry minecraft:worldgen/template_pooldata/<ns>/worldgen/template_pool/*.json
place structureminecraft:resource_key, registry minecraft:worldgen/structuredata/<ns>/worldgen/structure/*.json
place templateminecraft:resource_location — no registrydata/<ns>/structure/*.nbt
⚠ Gotcha

place structure and place template do not share a namespace. The first wants a registry entry — the JSON structure definition under worldgen/structure/. The second wants a file path — the raw .nbt build under structure/. A pack routinely contains both under the same id, and checking one against the other reports a structure that plainly works as missing. The parser is the discriminator: resource_key carries a registry property naming exactly which registry, while resource_location carries none and means a path.

💡 Best practice

Prototype spawning with /place structure and /locate structure before relying on natural generation. Keep a tiny fallback / end-cap piece in every pool so truncated chains look intentional, and store rotation-sensitive logic behind an anchor block/entity so mechanics survive the random rotation.

Sources