Skip to main content

Episode 1


This tutorial covers the basic project setup, the mod class as well as the first block. If some code in here is not clear or not complete you can always refer to the GitHub as well.

Basic Project Setup

To start your own mod the easiest way is to download the latest Forge MDK from the Forge download site and extract it to some temporary folder. Then make a new directory for your own mod and copy over the following files from the MDK:

  • The gradle folder
  • The src folder
  • gradlew.bat and gradlew
  • settings.gradle, build.gradle, and
  • .gitignore

Then open build.gradle in your IDE (IntelliJ for example) as a project(!). Make sure to set the Java to JDK 17!

You probably want to change your modid. This should be a lowercase identifier containing only characters, digits and possibly an underscore. These are the places where you have to change the modid:

  • The main mod file. In the MDK that's called 'ExampleMod' but you can rename it to a better name. Also, probably rename the package

Starting with the Forge MDK for 1.20 all things that can be configured for a project are set in the file. build.gradle has special tasks to make sure that the values set here are properly propagated to mods.toml and other places.


Minecraft is distributed in an obfuscated manner. That means that all names of methods, fields, and variables are renamed to meaningless names. ForgeGradle can deobfuscate this for you. However, it needs to know which mappings to use. For modern Minecraft there are basically two popular ways to do this:

  • official: mappings from Mojang
  • parchment: mappings from Mojang with additional parameters and documentation

More information on Parchment can be found here

JEI and TOP dependencies

For development, it's nice to have JEI and TOP available. To do that you can change the following in your build.gradle. First change the repositories like this:

repositories {
    // Put repositories for dependencies here
    // ForgeGradle automatically adds the Forge maven and Maven Central for you

    maven { // JEI
        url ""
    maven { // TOP
        url ""

Then change dependencies to this:

dependencies {
    // Specify the version of Minecraft to use. If this is any group other than 'net.minecraft', it is assumed
    // that the dep is a ForgeGradle 'patcher' dependency, and its patches will be applied.
    // The userdev artifact is a special name and will get all sorts of transformations applied to it.
    minecraft 'net.minecraftforge:forge:1.18.1-39.0.5'

    // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings
    // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
    compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}")
    compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}-forge-api:${jei_version}")
    runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")

    implementation fg.deobf(project.dependencies.create("mcjty.theoneprobe:theoneprobe:${top_version}") {
            transitive = false

After making all these changes you need to refresh gradle ('gradle' tab on the top right)

Generating the runs

To be able to run Minecraft from within IntelliJ you can also need to run the 'genIntellijRuns' task (also in the gradle tab). This will generate 'runClient', 'runServer', and 'runData' targets. For now, we'll use 'runClient' mostly. Try it out and if all went well you should see Minecraft If this was successful you should see something like this:

Make sure that you're using Java 17!


The Basic Mod Class

There are many ways to structure your mod. In this base tutorial we follow the structure from the MDK. In future tutorials we will restructure this a bit. So here is our main mod class:

// The value here should match an entry in the META-INF/mods.toml file
public class Tutorial1Basics {
    // Define mod id in a common place for everything to reference
    public static final String MODID = "tut1basics";
    // Directly reference a slf4j logger
    private static final Logger LOGGER = LogUtils.getLogger();

    // Create a Deferred Register to hold Blocks which will all be registered under the "tut1basics" namespace
    public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID);
    // Create a Deferred Register to hold Items which will all be registered under the "tut1basics" namespace
    public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);

    // Creates a new Block with the id "tut1basics:example_block", combining the namespace and path
    public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));
    // Creates a new BlockItem with the id "tut1basics:example_block", combining the namespace and path
    public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties()));

    public Tutorial1Basics() {
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();

        // Register the commonSetup method for modloading

        // Register the Deferred Registers to the mod event bus so blocks and items get registered

        // Register ourselves for server and other game events we are interested in

        // Register the item to a creative tab

    private void commonSetup(final FMLCommonSetupEvent event) {
        // Some common setup code"HELLO FROM COMMON SETUP");"DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT));

    private void addCreative(BuildCreativeModeTabContentsEvent event) {
        if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) {

    // You can use SubscribeEvent and let the Event Bus discover methods to call
    public void onServerStarting(ServerStartingEvent event) {
        // Do something when the server starts"HELLO from server starting");

    // You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
    @Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
    public static class ClientModEvents {
        public static void onClientSetup(FMLClientSetupEvent event) {
            // Some client setup code
  "MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());

Minecraft Concepts

In the following image there are three columns:

  • Definitions: these are objects of which there is only one instance in the game. There is (for example) only one diamond sword. If you have two diamond swords in your inventory they are two different '''ItemStack''' instances referring to the same diamond sword item instance. This is important!
  • Inventory: all objects in an inventory (player or other containers) are represented with ItemStacks. An ItemStack is an actual in-game instance of an item. Note: in order to be able to hold blocks in your inventory the block needs a corresponding item
  • World: when blocks are placed in the world they are placed as a BlockState. A BlockState is a specific configuration of a block. For example, a furnace can have six orientations. Those are six different blockstates. In addition, a furnace can also be powered or not. So that means in total 12 different blockstates. '''Block Entities''' are objects that help extend blocks in the world to be able to hold more information (like inventory) as well as do things (tick).



Forge documentation:

Minecraft runs on two sides: the client and the server. The client is the side that the player sees and interacts with. The server is the side that runs the game logic. Note that even in single player there is a server. This we call the integrated server. Read the forge documentation linked above for more detailed information. Note that this is a very important thing to understand well.


Forge documentation:

Events are a very important concept in Forge. They are used to hook into the Minecraft game at various points. There are two main categories of events:

  • Mod events: these are events that are fired on the Mod event bus. This bus is used for listening to lifecycle events in which mods should initialize
  • Forge events: these are events that are fired on the Forge event bus. This bus is used for listening to events that happen in the game

Some examples of events are:

  • FMLCommonSetupEvent: this event is fired when the game is starting up. This is the place where you want to do most of your setup
  • FMLClientSetupEvent: this event is fired when the client is starting up. This is the place where you want to do client-side setup
  • BuildCreativeModeTabContentsEvent: this event is fired when the creative mode tabs are being built. This is the place where you want to add your items to the creative tabs

The events above are all fired on the Mod event bus. There are also events on the Forge event bus:

  • ServerStartingEvent: this event is fired when the server is starting. This is the place where you want to do server-side setup
  • EntityJoinLevelEvent: this event is fired when an entity is joining a world
  • BlockEvent.BreakEvent: this event is fired when a block is being broken

But there are many more events. You can find them in your IDE by looking at the net.minecraftforge.event package.

Events that implement IModEventBus are fired on the Mod event bus.

A popular image that is often posted on the Forge Discord is this one. Very often people have trouble with their events. Often the problem is that their method is static or not static when it should be the other way around.


Registration and Timing

Forge documentation:

Forge follows very specific timing rules for when you have to do certain things during mod setup. You can't just register stuff at any time that you want. For every kind of thing that you register there is a specific time when you have to do that and this is controlled with events.

The DeferredRegister is a very easy way to handle registration of various objects in the Minecraft game (blocks, items, containers, dimensions, entities, ...). It's important to note that in this register we will always register singletons. i.e. the objects in the 'Definition' column of our previous image. For every object that we want to add to our mod we declare a RegistryObject and then register it on the appropriate deferred register. In that registration we also give a supplier (lambda) to actually generate the instance of our registry object at the appropriate time.

Note: objects are registered pretty early. That means that at the time the FMLCommonSetupEvent is fired all objects from all mods will be registered and ready.

Because registration of objects happens very early it happens before config is handled. That means that you cannot depend on configuration values during registration! Don't register conditionally! If you must use other ways to disable your content (like hide from JEI, disable recipes, ...)

Note how we make a corresponding item (using BlockItem) for every block. That's because we need to be able to hold these blocks in our inventory (in case someone does silk touch on them).

In this specific example we use the standard vanilla Block and Item classes. Later we will show you how you can make your own custom blocks and items using subclasses.

Data Generation

Forge documentation:

If we run our mod now you will see that the blocks and items are not correctly textured and that the blocks don't have a good name. To fix that we need to make models and a bunch of other JSON files. We will be using data generation to generate those as that's the most flexible way to do things. With only a small mod it may not seem very beneficial to do this but in the end it's a very nice technique and will help you avoid many errors caused by handwritten JSON files. Data generation will be covered in the next episode.