Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 42 — Biome Customization

What You’ll Build

A biome is a region of a generated world with its own distinct geography, plants, mobs, temperature, humidity, and colors: forests, deserts, oceans, the Nether wastes. The biome of a location is determined during world generation rather than by the current environment. That last part is the key to this whole chapter, and we’ll come back to it.

Until now you’ve added files to registries that take effect the moment you /reload: recipes, loot tables, advancements, tags. World generation is different. In this chapter you’ll write a biome definition (a JSON file under data/<namespace>/worldgen/biome/) that defines a brand-new biome called the Frozen Wasteland: bone-cold temperature, falling snow, pale washed-out water and grass, and a spawners block that fills it with hostile mobs. You’ll learn the full Java biome-definition field shape, why a biome needs a world reboot rather than a /reload, how biome tags group biomes and what they’re used for, and finally what those cat/variant and wolf/variant item components from Chapter 24 actually point at: the mob-variant definition registries.

By the end you’ll have a registered custom biome (whether or not it shows up in your world yet, that comes in Chapter 44), and you’ll understand exactly how far the standard fields take you and where you’d reach for the live wiki to go further.

Figure (to be captured). a player standing in a custom pale-blue snowy biome with washed-out grass, a skeleton and a husk visible nearby

Modern Minecraft. Worldgen files used to live behind the dreaded “experimental” warning. In the registry map you saw in Chapter 38, the worldgen/biome folder (like every worldgen folder) carries a small red asterisk. That asterisk means a pack using that folder is flagged as using experimental settings (you’ll get a warning screen when you create the world, and Realms won’t take it). The feature itself is real and documented; it’s the folder that trips the flag, exactly like the dialog folder back in Chapter 39.

What a Biome Is (and Real Biome Names, At Last)

Twice before, this book has used a biome ID and then quietly admitted it couldn’t fully prove it. In Chapter 18 we wrote a predicate that fired in the dark forest and hedged the ID minecraft:dark_forest. In Chapter 31 we avoided biome literals entirely. This chapter closes that gap, naming the real biomes directly.

In Java Edition there are 66 biome types: 55 for the Overworld, 5 for the Nether, and 5 for the End, plus one used only for a superflat preset. Each biome has its own resource location (its ID) of the familiar minecraft:<name> shape. Dozens of these biomes have names you’ll recognize: the plains, the dark forest, snowy plains, ice spikes, deserts, swamps, jungles, cherry groves, the pale garden, and so on, all real biomes you’ll see in-game. There’s an important difference between knowing a biome’s name and knowing its exact ID. The wiki’s Biome/ID page lists every Java biome beside its exact resource location (more on that in a moment). Two of those IDs you can confirm a second way, which makes them worth calling out:

  • minecraft:dark_forest — the dark forest biome is mainly composed of dark oak trees (woodland mansions can generate here), and dark_forest is one of the allowed values of the grass_color_modifier field you’ll meet below. The registry name dark_forest shows up verbatim as a field value, so this one is solidly grounded, and that lays the Chapter 18 hedge to rest.
  • minecraft:plains — appears as a full ID literal in the game’s feature-ordering rules (in the UNDERGROUND_ORES step of minecraft:plains, ore_dirt is placed before ore_gravel). So plains is a confirmed ID too.

Resolving the gap (for real this time). The wiki’s Biome/ID page carries the complete Java Edition table: every biome paired with its exact resource location, from minecraft:the_void and minecraft:plains through minecraft:dark_forest, minecraft:cherry_grove, minecraft:deep_dark, the Nether five (nether_wastes, warped_forest, crimson_forest, soul_sand_valley, basalt_deltas), and the End set (the_end, end_highlands, end_midlands, small_end_islands, end_barrens). So the Chapter 18 hedge is fully closed: minecraft:dark_forest is real, and so is every other ID you’ll reach for. Two of them you can double-confirm a second way: dark_forest is also an allowed grass_color_modifier value, and plains appears as a literal ID in the feature-ordering example above. (One genuine caution worth keeping: the strings minecraft:swamp, minecraft:frozen_ocean, and minecraft:the_end also appear as Bedrock surface_builder type values, so a name matching a biome isn’t proof of the biome’s ID. The Biome/ID table is the place to confirm the Java resource location.)

What actually makes a biome feel like a biome? A handful of properties do, and these are exactly the fields you’re about to write:

  • Temperature — a number that drives grass and foliage color, and (height-adjusted) whether it rains or snows.
  • Downfall — a humidity number between 0.0 and 1.0, mainly used for block colors; above 0.85 the biome counts as “humid.”
  • Precipitation — in Java Edition, simply on or off (true/false), separate from downfall.
  • Effects — the colors of water, grass, and foliage.
  • Spawns — which mobs appear, and how often.
  • Features and carvers — the trees, ores, lakes, and caves carved into the terrain (Chapter 43).

The Biome Definition File

Biome definitions are stored as JSON files within a data pack in the path data/<namespace>/worldgen/biome. A file at data/mypack/worldgen/biome/frozen_wasteland.json therefore becomes the biome mypack:frozen_wasteland: the same registry-folder rule you’ve used since Chapter 7, just a deeper folder.

Here is every Java field, top to bottom. Read it once now; the next section builds a real file using these.

Root fields:

  • has_precipitation — a true/false boolean: whether the biome has precipitation at all.
  • temperature — a float (decimal number) that “controls gameplay features like grass and foliage color, and a height adjusted temperature (which controls whether raining or snowing occurs if has_precipitation is true).”
  • temperature_modifier — optional, defaults to none. Either none or frozen. When frozen, it “makes some places’ temperature high enough to rain (0.2)”. This is the trick the frozen ocean uses so a few patches don’t freeze.
  • downfall — a float that “controls grass and foliage color.”
  • effects — a compound (an object) holding the biome’s ambient colors. Its fields:
    • water_color — required, “the normal value is 4159204.” A decimal number converted from a hex color, used for water blocks and cauldrons.
    • foliage_color — optional. Decimal color for tree leaves and vines. If absent, it’s derived from downfall and temperature.
    • dry_foliage_color — optional. Decimal color for leaf litter.
    • grass_color — optional. Decimal color for grass blocks, grass, ferns, and sugar cane. If absent, derived from downfall and temperature.
    • grass_color_modifier — optional, defaults to none. One of none, dark_forest, or swamp (these apply the special grass tints those biomes use).
  • carvers — required, but can be empty. The cave/ravine carvers for this biome. We’ll write {} and leave carvers for Chapter 43.
  • features — a list of generation steps (can be empty). Each step is itself a list of placed features to run during that step. There are eleven step names, in order: RAW_GENERATION, LAKES, LOCAL_MODIFICATIONS, UNDERGROUND_STRUCTURES, SURFACE_STRUCTURES, STRONGHOLDS, UNDERGROUND_ORES, UNDERGROUND_DECORATION, FLUID_SPRINGS, VEGETAL_DECORATION, and TOP_LAYER_MODIFICATION. (You build the things that go inside these steps in Chapter 43.)
  • creature_spawn_probability — optional float between 0.0 and 0.9999999; higher means more creatures spawned during world generation.
  • spawners — required, but can be empty. This is the heart of “what spawns here.” It’s a compound whose keys are mob categories and whose values are lists of spawn entries.
  • spawn_costs — required, but can be empty. Only mobs listed here use the “spawn cost” mechanism (a way to limit dense spawning); each entry has an energy_budget and a charge.

Inside spawners — the mob categories. Each key must be one of monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, or axolotls. If a category is missing or its list is empty, mobs in that category simply don’t spawn. Each entry in a category’s list is the spawner data for a single mob:

  • type — the namespaced entity ID of the mob (e.g. minecraft:skeleton).
  • weight — an int: how often this mob spawns; higher values produce more spawns.
  • minCount — an int greater than 0: the minimum size of a spawned pack.
  • maxCount — an int not less than minCount: the maximum pack size.

Under the Hood (skippable). Notice minCount and maxCount use capital letters in the middle. That’s “camelCase,” and it’s unusual for data-pack JSON, which almost always uses lowercase snake_case. Worldgen inherited a few old names like these. Type them exactly as shown; JSON cares about capitalization.

Those are the effects fields we’ll use here. Real biomes also carry a few more effect fields: a sky_color, a fog_color and water_fog_color, a mood_sound and additions_sound, music, and ambient particles. They drive the daytime sky tint (computed from temperature), the fog color, the cave ambience, and the pale garden’s silence. We’ll build a vivid biome with the color fields alone, which is plenty to see the system working; when you want to add the rest, open the wiki’s Biome page or copy a vanilla biome and write the extra fields you find there.

Walkthrough: The Frozen Wasteland

Let’s build it. We need a single file. Create the folders worldgen/biome inside your mypack data folder and add this file.

data/mypack/worldgen/biome/frozen_wasteland.json

{
  "has_precipitation": true,
  "temperature": -0.7,
  "temperature_modifier": "none",
  "downfall": 0.4,
  "effects": {
    "water_color": 3750201,
    "foliage_color": 11445290,
    "grass_color": 8434339
  },
  "carvers": {},
  "features": [],
  "creature_spawn_probability": 0.05,
  "spawners": {
    "monster": [
      {
        "type": "minecraft:skeleton",
        "weight": 100,
        "minCount": 1,
        "maxCount": 4
      },
      {
        "type": "minecraft:husk",
        "weight": 80,
        "minCount": 1,
        "maxCount": 3
      }
    ],
    "creature": []
  },
  "spawn_costs": {}
}

Walk through it against the field list:

  • "has_precipitation": true with a very low "temperature": -0.7 means precipitation falls as snow: when the base temperature is below 0.15, a biome is snowable at any height. That’s our frozen wasteland’s weather.
  • temperature_modifier is none here. (Set it to frozen if you wanted scattered unfrozen patches like the frozen ocean; we want it uniformly icy.)
  • The three effects colors are decimal numbers. 3750201 is a deep cold-blue water; 11445290 and 8434339 are pale, washed-out foliage and grass. These are ordinary decimal-from-hex color numbers, exactly the kind you’ve converted since Chapter 21’s custom_model_data colors. water_color is the one effect field that’s required, so it must be present.
  • "carvers": {} and "features": [] are the empty-but-required containers. Our biome generates with no special caves and no trees or ores of its own: a true wasteland. (Chapter 43 fills these.)
  • creature_spawn_probability is low (0.05) because a wasteland should feel barren of passive life.
  • spawners is where the danger lives. The monster category lists skeletons (weight 100) and husks (weight 80), each spawning in small packs. weight is relative: skeletons appear a bit more often than husks. The creature list is present but empty, so no passive animals spawn here. We left the other six categories out entirely, which means those categories don’t spawn either.
  • spawn_costs is the required-but-empty {}; we’re not using the spawn-cost limiter.

Every field above is a real biome-definition field. Save the file.

Try It! Want a hint of life among the bones? Add a third category to spawners: "ambient": [ { "type": "minecraft:bat", "weight": 10, "minCount": 1, "maxCount": 2 } ]. Bats are in the ambient category, so they belong there, not in creature.

Why a /reload Isn’t Enough

Now the catch we flagged at the top. A location’s biome is determined during world generation rather than by the current environment, even if every block in a large area is altered to imitate the terrain of another biome. A biome is baked into the world as it generates.

This connects to the dynamic-registry idea from Chapter 9. Most registries you’ve written to (recipes, loot tables, functions) are re-read every time you run /reload. Worldgen registries (biomes, dimensions, and the enchantments you saw in Chapter 35) are dynamic registries that are loaded when a world is created or opened, not on /reload. Editing frozen_wasteland.json and typing /reload will not make the biome appear or update.

To get a biome definition into the registry:

  1. Make sure your pack (with the worldgen/biome file) is installed in the world’s datapacks folder before you open the world. The cleanest way: put the pack in a fresh world’s datapacks folder and create the world.
  2. If you edit the file later, you must close and reopen the world (or remake it) for the change to load, not /reload.

What Went Wrong? “I made the biome, ran /reload, and /locate biome mypack:frozen_wasteland says it doesn’t exist.” A /reload doesn’t reload worldgen. Quit to the title screen and reopen the world. If it still isn’t found, the file has a JSON typo and failed to load. Check the game log (Chapter 10) for a worldgen error naming your file.

And one honest limitation: registering a biome is not the same as making it appear in your Overworld. The Overworld decides which biomes go where using a biome source that’s part of its dimension. A freshly registered custom biome sits in the registry, ready, yet won’t show up until a dimension is told to place it. That’s Chapter 44’s job. For now you can still prove the biome loaded:

data/mypack/function/find_frozen_wasteland.mcfunction

locate biome mypack:frozen_wasteland

In a vanilla Overworld this will report the biome isn’t found nearby (because nothing places it yet), but if it loaded into the registry the command will be recognized rather than erroring on an unknown biome, your first sign the file is valid. (You’ll make it actually generate in Chapter 44.)

Biome Tags and Their Uses

You’ve grouped registry entries with tags since Chapter 14. Biomes get tags too. A biome tag is simply a group of biomes, and it has three concrete uses:

  1. Controlling where structures generate.
  2. Setting the spawn conditions of various entities, including, as you’ll see in a moment, which variant of an animal spawns.
  3. Testing biomes in commands. A biome tag can be used when testing for biome arguments in commands with #<resource location>, which succeeds if the biome matches any of the biomes specified in the tag. So #minecraft:is_forest matches every forest biome.

Real biome tags include is_overworld, is_forest, is_badlands, is_ocean, is_taiga, is_mountain, is_river, is_nether, is_end, is_jungle, and is_beach. A biome-tag file looks like any other tag file (Chapter 14), a list of biome IDs, and lives at data/<namespace>/tags/worldgen/biome/<name>.json.

Try It! Make your own biome group and put the wasteland in it:

data/mypack/tags/worldgen/biome/spooky.json

{
  "replace": false,
  "values": [
    "mypack:frozen_wasteland"
  ]
}

Now #mypack:spooky is a valid biome test in any command that takes a biome argument. (Like the biome itself, this is worldgen-adjacent data, so reopen the world after adding it.)

Mob-Variant Definition Registries

Back in Chapter 24 you met a quietly important kind of item component: the minecraft:<mob>/variant strings. A cat carries minecraft:cat/variant; a wolf carries minecraft:wolf/variant and a separate minecraft:wolf/sound_variant; cows, chickens, frogs, and pigs each have their own. We told you those strings name a variant but deferred where the variant is defined to this chapter. Here it is.

In modern Minecraft, a mob’s appearance variants are their own dynamic registries, each with its own data-pack folder, right alongside worldgen/biome in the registry map you read in Chapter 38. Here they are, every one marked with the experimental red asterisk:

data/<namespace>/
  cat_variant              * Textures and spawn conditions of cat variants
  chicken_variant          * Textures and spawn conditions of chicken variants
  cow_variant              * Textures and spawn conditions of cow variants
  frog_variant             * Textures and spawn conditions of frog variants
  pig_variant              * Textures and spawn conditions of pig variants
  wolf_variant             * Textures and spawn conditions of wolf variants
  wolf_sound_variant       * Sound variants of wolves
  zombie_nautilus_variant  * Textures and spawn conditions of zombie nautilus variants

So the wiring is: a file at data/mypack/wolf_variant/glacier.json would register the wolf variant mypack:glacier, and the item component minecraft:wolf/variant on a wolf is a string that points at that ID. The component side matches exactly: for the wolf, minecraft:wolf/variant is a wolf variant definition (the variant of the wolf), and likewise for cat/variant, chicken/variant, cow/variant, frog/variant, and pig/variant. The phrase “spawn conditions” in the table above is the link back to biomes: the game uses biome tags to decide which variant spawns where. The deciding tags are right there in the biome-tag list: spawns_cold_variant_farm_animals, spawns_warm_variant_farm_animals, spawns_cold_variant_frogs, spawns_warm_variant_frogs, and spawns_coral_variant_zombie_nautilus. That’s how a cow “knows” to be the warm variant in a jungle and the cold variant in a taiga: the biome’s tags steer it.

You can set a variant directly on a spawned mob, using the very component from Chapter 24. Here’s a test function that gives you a wolf already locked to a specific variant via its component:

data/mypack/function/give_test_wolf.mcfunction

give @s minecraft:wolf_spawn_egg[minecraft:wolf/variant="minecraft:pale"]

The minecraft:wolf/variant component holds a string, a variant ID. Here it’s a vanilla one. If you had authored mypack:glacier in wolf_variant/, you’d write minecraft:wolf/variant="mypack:glacier" instead, and a wolf spawned from that egg would wear your variant.

So you’ve seen the folders, the item components that reference them, and the biome tags that steer them. The one piece left is the inside of a variant definition file: which textures it points at, which spawn-condition keys it uses, its asset model fields. Those internal fields are the variant system’s own deep end: for cat, chicken, cow, frog, pig, wolf, wolf-sound, and zombie-nautilus variants, when you want to author a full custom variant, open the live wiki’s “Mob variant definitions” page or copy a vanilla variant file and write the fields you find there. (One handy exception: minecraft:horse/variant isn’t a definition file at all but a fixed list of values: white, creamy, chestnut, brown, black, gray, or dark_brown.)

Modern Minecraft. If you followed older tutorials, you may have seen cat or wolf variants set with raw NBT, or grouped with a cat_variant tag. Those tags still exist for compatibility, but the old default_spawns / full_moon_spawns cat-variant tags were replaced by spawn condition: the modern variant system uses spawn conditions (and biome tags) for this grouping now.

Practice

1. A second biome — Scorched Flats. Build the opposite of the wasteland: blistering, dry, no precipitation. Reuse the exact field shape from the walkthrough.

data/mypack/worldgen/biome/scorched_flats.json

{
  "has_precipitation": false,
  "temperature": 2.0,
  "downfall": 0.0,
  "effects": {
    "water_color": 4566514,
    "foliage_color": 10387789,
    "grass_color": 12431967,
    "grass_color_modifier": "none"
  },
  "carvers": {},
  "features": [],
  "creature_spawn_probability": 0.0,
  "spawners": {
    "monster": [
      {
        "type": "minecraft:husk",
        "weight": 100,
        "minCount": 2,
        "maxCount": 4
      }
    ]
  },
  "spawn_costs": {}
}

Note "has_precipitation": false and "downfall": 0.0 (no rain or snow at all) and a temperature of 2.0, the kind of value deserts use. We dropped temperature_modifier entirely (it’s optional and defaults to none), creature_spawn_probability is 0.0, and only husks spawn. Confirm it loads with locate biome mypack:scorched_flats after reopening the world.

2. Tag both your biomes. Extend data/mypack/tags/worldgen/biome/spooky.json (or make a new harsh.json) to list both mypack:frozen_wasteland and mypack:scorched_flats, then use #mypack:harsh as a biome test in a command of your choice.

3. Read a variant’s wiring. Without authoring a variant definition (the book can’t), write a give function that hands you a cat spawn egg whose minecraft:cat/variant component is set to a vanilla variant ID of your choice. You’re practicing the component → variant-ID link from Chapter 24, now that you know what the ID refers to.

What Can Go Wrong

  • You typed /reload and nothing happened. Worldgen is a dynamic registry. Biome (and variant) files load when the world is created or opened, never on /reload. Quit to the title and reopen the world.
  • You used a hex color string for water_color. The color fields are decimal integers, not "#aabbcc" strings. Convert your hex to a decimal number first (the same conversion you’ve done for item colors since Chapter 21). water_color is also required; leaving it out fails the file.
  • You put a passive animal in the monster category (or invented a category). The category key must be exactly one of the eight valid keys (monster, creature, ambient, water_creature, underground_water_creature, water_ambient, misc, axolotls). A typo’d category name silently means “nothing spawns there.”
  • You expected the biome to appear in your Overworld. Registering a biome doesn’t place it. A dimension’s biome source decides where biomes generate: that’s Chapter 44. Until then, locate recognizing the ID is your proof of a valid file.
  • You tried to write the inside of a wolf_variant file from this book. This chapter stops at the folder boundary for those internal fields. Name and reference variants with confidence; author their definition bodies from the live wiki.