Custom biomes in 1.21.1 are fully data-driven: the biome definition is a JSON file in data/<namespace>/worldgen/biome/, and injecting it into the overworld requires knowing the climate parameter space the world generator uses to pick biomes. This tutorial covers creating the biome JSON, generating it with datagen, and injecting it into the overworld using TerraBlender, the standard multiloader library for custom biome placement.
DatapackBuiltinEntriesProvider, which is used again here for biome datagen.The Biome JSON
Create the file src/main/resources/data/examplemod/worldgen/biome/example_biome.json in your common project. The full structure of a biome JSON:
The fields in detail:
- temperature: Float, 0.0 to 2.0. Controls whether precipitation falls as rain (above 0.15), snow (below 0.15), or nothing (above 1.0 in the case of some desert variants). Also affects which farmland crops grow without irrigation.
- downfall: Float, 0.0 to 1.0. Controls grass and foliage colour interpolation and the wetness of the biome.
- has_precipitation: Boolean. Set to false for dry biomes like deserts and badlands.
- temperature_modifier:
"none"or"frozen". The frozen modifier is used by frozen ocean variants to make surface water freeze regardless of altitude.
Biome Effects
The effects block controls all visual and audio properties:
- sky_color: Decimal integer encoding an RGB colour for the sky. Use an online hex-to-decimal converter; the vanilla forest sky is
7972607(#79a8ffroughly). - fog_color: The distant fog colour. Most overworld biomes use
12638463. - water_color: The tint applied to water blocks.
4159204is vanilla ocean blue. - water_fog_color: The underwater fog tint.
- grass_color_modifier:
"none","dark_forest", or"swamp". Dark forest applies a multiplicative darkening; swamp shifts grass to a muted yellow-green. - foliage_color: Optional. When absent, foliage colour is derived from temperature and downfall. Provide an integer to override it entirely.
- grass_color: Optional. Same as foliage_color but for grass blocks.
- mood_sound: The ambient cave sound event, how many ticks between plays, the block search radius, and the spatial offset from the listener.
- ambient_sound: Optional. A looping background sound (used by the Nether biomes).
- additions_sound: Optional. A randomly played addition sound alongside the ambient sound.
- music: Optional. Background music configuration (sound, min/max delay, whether to replace current music).
- particle: Optional. A particle that appears randomly in the air, like nether ash or warped spores.
Mob Spawning
The spawners block lists mob spawn entries per mob category. Each entry requires a type (the entity registry name), a weight (relative frequency compared to others in the same category), and minCount and maxCount (the pack size range):
spawn_costs is the energy-based spawn limiting system for mobs that use it (wolves, foxes, and similar animals). Most modded mobs do not need an entry here unless they extend one of the vanilla entity types that uses it.
Registering the Biome Key
Create a BiomeKeys class in your common project to hold the ResourceKey for your biome. This key is used both in datagen and at runtime when referencing the biome from code:
Datagen via DatapackBuiltinEntriesProvider
Instead of writing the JSON by hand, use the Java builder API in a bootstrap function. This is particularly useful for biomes because the feature list is long and error-prone to type manually. Create a bootstrap class:
Register it in your DatapackBuiltinEntriesProvider:
Adding the Biome to the World
Creating the biome JSON makes the biome exist as a registry entry, but it does not cause it to generate anywhere in the world. World generation in 1.21.1 uses a noise-based climate parameter space to select biomes: every point in the world has values for temperature, humidity, continentalness, erosion, weirdness, and depth, and the biome source maps those values to a biome choice.
Injecting a new biome into the overworld requires either writing a custom BiomeSource (very complex, and the existing source cannot be easily replaced without affecting all other mods) or using TerraBlender, a library that provides a safe region-based API for both loaders. TerraBlender is the de facto standard for overworld biome injection in 1.21.1 and supports both Fabric and NeoForge from a single codebase.
BiomeModifier system or Fabric's BiomeModifications.addProperties(). The TerraBlender approach described below targets the overworld only.TerraBlender Setup
Add TerraBlender as a dependency in each loader subproject. Find the latest version numbers on Modrinth:
On NeoForge, add TerraBlender to your mods.toml dependencies block so it is listed as a required dependency. On Fabric, add it to fabric.mod.jsonunder depends.
compileOnly dependency to your common module. This gives you compile-time access to the shared Region and Regions classes without pulling in the full runtime on the common classpath.Defining a Region
TerraBlender organises biome injection around regions. A region defines which biomes it contributes to the climate parameter space and how heavily it participates (the weight). A weight of 1 means the region occupies roughly 1/(1 + total vanilla weight) of the biome selection probability. Increase it to make your biome appear more frequently.
Create a ExampleBiomeRegion class in your common project:
Alternatively, you can manually specify the climate parameters where your biome should generate. Climate parameters range from -1.0 to 1.0 on each axis. Use Climate.Parameter.span(min, max) for ranges and Climate.Parameter.point(value) for exact values:
Register the region from your common initialiser. Because TerraBlender is a required dependency on both loaders, this call works in common code with no loader-specific branching:
The integer argument is the weight. A value of 2 gives your biome roughly twice as much representation as a region with weight 1. Start with 1-2 for a biome that appears occasionally, and increase it if the biome is too rare during testing.
addModifiedVanillaBiomes helper replaces specific vanilla biome points rather than adding entirely new ones, which keeps the total climate parameter space balanced. Use the manual addBiome call if you want your biome to appear in areas where no vanilla biome would otherwise generate, such as very unusual climate combinations. Avoid overlapping climate ranges heavily with vanilla biomes, as this can make vanilla biomes disappear from the world.Lang Entry
Add a translation entry for the biome name. This is displayed in the F3 debug screen and in any context that shows the current biome (such as the /locate biomecommand output):
Testing
Create a new world with both TerraBlender and your mod installed. Run /locate biome examplemod:example_biome in chat. Minecraft will search the surroundings and report the nearest coordinates of your biome. Teleport there to confirm it generates with the correct sky colour, fog, water colour, and mob spawns.
If the /locate biome command reports "biome not found", check:
- That the JSON file path is correct:
data/examplemod/worldgen/biome/example_biome.json. - That
Regions.register()was called before the world is loaded. - That TerraBlender is installed and loading correctly (check the mod list on the title screen).
- That you are searching in a newly generated world. Existing chunks in old worlds will not contain the new biome.
Use /tp @s ~ ~ ~ followed by pressing F3 to see the current biome name once you have teleported to the reported coordinates. The biome name should show as "Example Biome" in the debug overlay.
You can find the source for this tutorial here:
View Source on GitHub