Minecraft Modding Guide

Forge 1.18 Ore Generation Tutorial

This tutorial will cover spawning custom ores into the overworld in Minecraft 1.18 in Forge. This can be extended to other ore types, like deepslate, and other worlds, like the Nether.


The way that ores spawn in Minecraft has changed considerably in the 1.18 Caves and Cliffs update. The generation patterns are much more nuanced than in previous versions. The increased height limit has necessitated a change in the distributions for existing ores. Additionally, new large copper and iron ore veins can now be found at varying levels.

The chart below does a good job explaining how ore is distributed in 1.18 compared to previous versions.

1.18 ore generation patterns compared with 1.17 and previous. Source: https://minecraft.fandom.com/wiki/Ore?file=1-18-ore-distribution.jpg


  1. Basic Java and Forge knowledge
  2. Established Forge mod with general understanding of how it works
  3. A custom Block that you would like to use for ore generation

Making Custom Ores Generate in Forge 1.18

The first thing we need is a class to handle the ore generation. We will use Feature.ORE, and configure it in a few different ways for a few different types of ores.

public class OreFeature {
    public static Holder<PlacedFeature> OVERWORLD_OREGEN;
    public static void registerOreFeatures() {
        OreConfiguration overworldConfig = new OreConfiguration(OreFeatures.STONE_ORE_REPLACEABLES, ModBlocks.OPAL_ORE.get().defaultBlockState(), 20);
        OVERWORLD_OREGEN = registerPlacedOreFeature("overworld_opal_ore", new ConfiguredFeature<>(Feature.ORE, overworldConfig),
                HeightRangePlacement.uniform(VerticalAnchor.absolute(0), VerticalAnchor.absolute(75)));

    private static <C extends FeatureConfiguration, F extends Feature<C>> Holder<PlacedFeature> registerPlacedOreFeature(String registryName, ConfiguredFeature<C, F> feature, PlacementModifier... placementModifiers) {
        return PlacementUtils.register(registryName, Holder.direct(feature), placementModifiers);

    public static void onBiomeLoadingEvent(BiomeLoadingEvent event) {
        if (event.getCategory() == Biome.BiomeCategory.NETHER) {
        } else if (event.getCategory() == Biome.BiomeCategory.THEEND) {
        } else {
            event.getGeneration().addFeature(GenerationStep.Decoration.UNDERGROUND_ORES, OVERWORLD_OREGEN);

There are a few things going on here. In registerOreFeatures() we're defining the OreConfiguration that will be used in the ConfiguredFeature. Then we're making a call to a method we define farther down called registerPlacedOreFeature to register the ConfiguredFeature.

It's worth noting that you can pass in as many or as few PlacemendModifiers as you want here, to control the conditions of generation. Here we are saying we want 100 veins to generate per chunk, using square placement, uniformly spread between Y levels of 0 and 75. These PlacementModifiers allow you to control the behavior of your ore generation, and you can even define your own to further customize.

In our event listener, onBiomeLoadingEvent, we are denoting that we want to generate this ore in any overworld biome. If you want to limit the biomes it can spawn in, this is the place to make those checks using the BiomeCategory.

Next we need to setup the main mod class to register the deferred registry and also add an event listener for BiomeLoadingEvent.

public TutorialMod() {

private void setup(final FMLCommonSetupEvent event) {
        event.enqueueWork(() -> {

That's it! Now you should be able to see your custom ore block generating naturally in the world. It makes life easier when you are first debugging this code if you choose a high rate per chunk and large vein size when spawning. Then you can enter spectator mode and search the world or specific biomes for your ore.

Interested in making mods using a visual-programming / low-code solution? Check out my app MC Mod Maker: