Wiki 9Minecraft Wiki 9Minecraft

Heightmap Noise and Terrain Queries in World Generation

How to align features with the terrain surface and read procedural noise in Bedrock add-ons.

5Sections
2Code examples
3Known errors

When generating custom features in Minecraft Bedrock Edition, hardcoding vertical coordinates will either bury your structures underground or leave them floating in the air. To place features naturally on the terrain, you must combine heightmap queries with Molang noise functions inside your feature rules and scatter features. It is also important to know the boundaries of the add-on system: while you can read the heightmap and sample procedural noise for feature placement, the base terrain heightmap itself cannot be directly rewritten through standard behavior pack JSON.

What Bedrock Exposes for Heightmaps and Noise

In Minecraft Bedrock Edition, the base shape of the world—its hills, valleys, and oceans—is calculated by the core C++ engine using internal noise generators. Unlike some other aspects of the game, the primary terrain density and heightmap noise parameters are hardcoded or locked behind internal engine definitions that standard behavior packs cannot override. You cannot currently drop a noise settings JSON file into a behavior pack to change how tall mountains generate or how deep oceans go.

Instead, add-on creators interact with the heightmap and noise systems during the feature decoration phase. Once the game has shaped the raw stone and surface blocks of a chunk, it runs feature rules and features. During this pass, Bedrock exposes the calculated surface elevations and built-in procedural noise generators to your JSON files through Molang queries. This allows you to drape custom vegetation, boulders, and ore pockets across the landscape in natural-looking clusters that respect the underlying terrain.

Reading the Surface with query.heightmap

The primary tool for finding the ground level during world generation is the Molang function query.heightmap(x, z). When evaluated inside a feature rule or a scatter feature, this query takes the world X and Z coordinates of the current column being decorated and returns the Y coordinate of the highest non-air block at that position (more specifically, the Y coordinate directly above the topmost motion-blocking or surface block, depending on the generation pass).

Inside distribution blocks, Bedrock populates two origin variables for the current decoration step: variable.worldx and variable.worldz. By passing these two variables directly into the heightmap query, you lock the vertical placement of your feature to the exact contour of the land. If you want to generate something a fixed distance beneath the surface—such as a shallow root system or buried treasure—you simply subtract an offset from the returned heightmap value.

Molang ExpressionWhat it returnsTypical Use Case
query.heightmap(variable.worldx, variable.worldz)The Y coordinate of the terrain surface at the current placement column.Placing trees, flowers, surface rocks, and structures directly on the ground.
query.heightmap(variable.worldx, variable.worldz) - 4Four blocks below the detected surface at the current column.Spawning shallow underground features like mud patches, clay disks, or buried ruins.
variable.worldx / variable.worldzThe absolute world X and Z coordinates of the feature's current evaluation point.Feeding coordinates into heightmap or noise queries so each block column gets a unique result.

Sampling Procedural Noise in Molang

If you place features using a simple random chance per chunk, they will appear evenly peppered across the biome. Real ecosystems form patches, groves, and clearings. To recreate this, you can sample continuous procedural noise inside your feature distribution expressions. Because noise functions return smooth gradients rather than erratic random numbers, neighboring X and Z coordinates will yield similar values, naturally grouping your features together.

Bedrock provides several noise queries in Molang, with query.noise(x, z) being the most versatile for two-dimensional surface mapping. It returns a floating-point value typically ranging between -1.0 and 1.0. A crucial detail when working with noise queries is coordinate scaling. If you pass raw world coordinates like variable.worldx directly into the function without dividing them, the noise frequency matches the block grid, meaning the gradient shifts completely every single block. By dividing the world coordinates by a scale factor (such as 32.0 or 64.0), you stretch the noise pattern out across multiple chunks, creating broad, cohesive zones where your feature is allowed to spawn.

Query FunctionBehaviorNotes
query.noise(x, z)Returns a smooth 2D gradient noise value at the given coordinates.Divide x and z by a float (e.g., 50.0) to create large patches across chunks.
query.surface_noise(x, y, z)Samples the 3D noise generator used for vanilla surface builder variations.Useful when trying to align custom surface blocks with vanilla podzol or coarse dirt patches.

Combining Noise and Heightmaps in a Feature Rule

The most efficient place to combine noise filtering and heightmap snapping is inside a feature rule file. In the distribution object, the iterations field accepts a Molang expression. By using a ternary conditional operator here, you can check the noise value at the chunk's origin or current column first. If the noise value falls above your threshold, the expression returns a positive number of placement attempts; otherwise, it returns zero, skipping the area entirely and saving performance.

The example below demonstrates a complete feature rule that places a custom boulder feature across plains biomes. It divides the world coordinates by 48.0 inside query.noise to establish wide regions where boulders can appear, and then uses query.heightmap on the Y axis so every boulder settles directly on the grass.

BP/feature_rules/overworld_boulder_patch_rule.json
{
  "format_version": "1.13.0",
  "minecraft:feature_rules": {
    "description": {
      "identifier": "wiki:overworld_boulder_patch_rule",
      "places_feature": "wiki:mossy_boulder_feature"
    },
    "conditions": {
      "placement_pass": "surface_pass",
      "minecraft:biome_filter": [
        {
          "test": "has_biome_tag",
          "operator": "==",
          "value": "plains"
        }
      ]
    },
    "distribution": {
      "iterations": "query.noise(variable.worldx / 48.0, variable.worldz / 48.0) > 0.35 ? math.die_roll(1, 1, 3) : 0",
      "x": {
        "distribution": "uniform",
        "extent": [ 0, 16 ]
      },
      "y": "query.heightmap(variable.worldx, variable.worldz)",
      "z": {
        "distribution": "uniform",
        "extent": [ 0, 16 ]
      }
    }
  }
}

Elevating and Clustering with Scatter Features

Sometimes you only want a feature rule to evaluate once per chunk, and then let a scatter_feature handle the individual block placements. When you move the distribution logic into a scatter feature, be aware of how coordinate evaluation order works. In a distribution block, X and Z are evaluated before Y, which is why variable.worldx and variable.worldz already hold the offset coordinates when the Y expression runs.

However, if you evaluate query.noise inside the iterations field of a scatter feature, variable.worldx and variable.worldz represent the origin passed into that scatter feature, not the individual scattered points. If you want each individual point in a scatter cluster to test against both the noise map and the heightmap independently, place the heightmap query in the Y coordinate and use project_input_to_floor: false (since you are manually calculating the floor), or rely on the scatter_chance field to check the noise at each offset position.

BP/features/alpine_shrub_scatter_feature.json
{
  "format_version": "1.13.0",
  "minecraft:scatter_feature": {
    "description": {
      "identifier": "wiki:alpine_shrub_scatter_feature"
    },
    "places_feature": "wiki:single_shrub_feature",
    "iterations": 12,
    "scatter_chance": "query.noise(variable.worldx / 24.0, variable.worldz / 24.0) > 0.1 ? 100.0 : 0.0",
    "x": {
      "distribution": "gaussian",
      "extent": [ -6, 6 ]
    },
    "z": {
      "distribution": "gaussian",
      "extent": [ -6, 6 ]
    },
    "y": "query.heightmap(variable.worldx, variable.worldz) > 95 ? query.heightmap(variable.worldx, variable.worldz) : -1000"
  }
}

Which versions this applies to

Applies to Bedrock Edition 1.13.0 and newer (including 1.20.x and 1.21.x). Feature rules, scatter features, query.heightmap, and query.noise are all stable and do not require any experimental toggles.

When it does not work

What you seeWhyThe fix
Features generate in a chaotic checkerboard pattern rather than smooth patches when using query.noise.You passed raw integer coordinates (variable.worldx, variable.worldz) into the noise query without scaling them down. At a 1:1 scale, the noise waves oscillate faster than the block grid.Divide Both X and Z coordinates by a floating-point scale number inside the query, such as query.noise(variable.worldx / 40.0, variable.worldz / 40.0).
Features placed with query.heightmap generate at the bottom of the ocean instead of floating on the water, or fail to place over water.During surface and underground passes, query.heightmap returns the top of the solid terrain (the ocean floor) or the water surface depending on the pass, and the placed feature itself may reject water blocks.Add an elevation check to your Y expression (for example, ensuring query.heightmap is above sea level at Y=63) or use a single_block_feature with may_replace rules that explicitly exclude minecraft:water.
Content Log shows: [Molang][error] Error: unhandled request for unknown variable 'variable.world_x'The built-in world coordinate variables in feature distributions do not have underscores before the axis letter.Change variable.world_x and variable.world_z to variable.worldx and variable.worldz.

Reference, straight from the game files

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