Skip to content

10. Custom Blocks & Mod Support

Bram Stout edited this page Mar 10, 2026 · 1 revision

This page provides a selection of tutorials detailing how to add in support for custom blocks, entities, and mods. Each tutorial looks at adding support for a specific block or entity.

Minecraft End Portal

The minecraft:end_portal block doesn't have a model file in Minecraft's default resource pack. Instead, the model is generated at runtime in Minecraft's code. To still be able to export it out, we need to provide a model for it. We can use Minecraft's blockstate and model files, but we're going to use MiEx's built-in blockstate format.

First, we create the file builtins/minecraft/blockstates/end_portal.json in a resource pack. The namespace folder and file name can be anything. In that file, we start of by specifying which blocks this file is for:

{
    "blocks": [ "minecraft:end_portal" ],
    "defaultTexture": "minecraft:block/end_portal",
    ...
}

With this, we are specifying that this file provides the blockstate and model for the block minecraft:end_portal. We also specify the resource identifier for the default texture, which is used by MiEx's viewport to generate a colour for that block.

Now we can add in the actual model, by specifying the model handler. The model handler syntax is somewhat similar to Minecraft Java Edition's model file format, but with many more features. We're going to start off by specifying a texture variable:

{
    "blocks": [ "minecraft:end_portal" ],
    "defaultTexture": "minecraft:block/end_portal",
    "handler": {
        "textures": {
            "texture": "'minecraft:block/end_portal'"
        },
        ...
    }
}

We are setting the handler property to a "model part", which is a JSON object that can hold things like texture variables, geometry elements, transformations, and other model parts. In this case, we are adding a texture variable called texture and setting it to 'minecraft:block/end_portal'. Notice the single quotation marks. The value is an expression. We can do calculations to create the texture resource identifier, but in this case we just want to set it to some hard-coded string. To specify a hard-coded string, we use single quotes.

Next up, we need the actual geometry. The geometry is just a single plane facing up.

{
    "blocks": [ "minecraft:end_portal" ],
    "defaultTexture": "minecraft:block/end_portal",
    "handler": {
        "textures": {
            "texture": "'minecraft:block/end_portal'"
        },
        "elements": [
            {
                "from": [ 0, 12, 0 ],
                "to": [ 16, 12, 16 ],
                "texture": "'#texture'",
                "faces": {
                    "up": {}
                }
            }
        ]
    }
}

Our model part now has an elements property which is a JSON array of "elements". Each element is a cube with a starting point and ending point. A block is in the coordinate range of (0, 0, 0) to (16, 16, 16). We also specify the texture which is also an expression that evaluates to a string holding the texture variable to use, which is #texture. Specifying texture in here will apply that texture for each face, but we could also specify a texture for each face specifically. Speaking about faces, by default all faces of the cube will be shown, but we are specifying the faces property which lets us choose which faces to show and optionally provide extra settings for those faces. In this case we just want the upwards face and we don't have any extra settings to provide for it.

And that will provide a simple model for minecraft:end_portal.

Shulker Boxes

Shulker boxes also do not have a model file, instead their models are handled in Minecraft's code due to the opening and closing animations. Because of this we need to provide our own models for the shulker boxes, which we are doing with MiEx's built-in blockstate format.

First, we create the file builtins/minecraft/blockstates/shulker_box.json in a resource pack. The namespace folder and file name can be anything. In that file, we start of by specifying which blocks this file is for:

{
    "blocks": [
        "minecraft:shulker_box", "minecraft:white_shulker_box", "minecraft:orange_shulker_box", 
        "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", 
        "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", 
        "minecraft:light_gray_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", 
        "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", 
        "minecraft:red_shulker_box", "minecraft:black_shulker_box"
    ],
    "defaultTexture": "minecraft:entity/shulker/shulker",
    ...
}

We can specify all of the shulker boxes in the blocks property to indicate that this file is going to provide the blockstate and models for all shulker boxes. This is a useful features of the built-in blockstate format. We also specify the default texture which is used by MiEx's viewport to generate a colour for the block to show.

Next up, we need to provide the model, so we're going add in a handler with a model part:

{
    "blocks": [
        "minecraft:shulker_box", "minecraft:white_shulker_box", "minecraft:orange_shulker_box", 
        "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", 
        "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", 
        "minecraft:light_gray_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", 
        "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", 
        "minecraft:red_shulker_box", "minecraft:black_shulker_box"
    ],
    "defaultTexture": "minecraft:entity/shulker/shulker",
    "handler": {
        "textures": {
            "texture": "'minecraft:entity/shulker/shulker'"
        },
        "elements": [
            {
                "from": [ 0, 0, 0 ],
                "to": [ 16, 8, 16 ],
                "entityUVs": [ 0, 7, 16, 13 ],
                "texture": "'#texture'",
                "faces": {
                    "down": {},
                    "east": {},
                    "north": {},
                    "south": {},
                    "west": {}
                }
            },
            {
                "from": [ 0.01, 4, 0.01 ],
                "to": [ 15.99, 16, 15.99 ],
                "entityUVs": [ 0, 0, 16, 7 ],
                "texture": "'#texture'"
            }
        ]
        ...
    }
}

In our model part, we add in a texture variable that points to the shulker texture. Then we add in two elements, providing the bottom part and top part of the shulker box. For the bottom part, we don't want the top face, so we specify faces and leave out the up face. For the top part, we want all of the faces so we can just leave out the faces property. By default, the UVs will be automatically generated based on the coordinates of the faces. We can specify custom UVs for each face, but we can also make use of the entityUVs property, which will create the UVs based on the layout seen with entities. The values are the UV bounding box that this element takes up in the texture.

The file as it currently is will work and will export out shulker boxes, but they will all be the default shulker box colour and won't be rotated to match the facing direction set in the block properties. The expressions come in handy for both of these cases.

For the textures, we can take the block's name and do a little bit of string manipulation to get the right texture variant:

{
    "blocks": [
        "minecraft:shulker_box", "minecraft:white_shulker_box", "minecraft:orange_shulker_box", 
        "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", 
        "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", 
        "minecraft:light_gray_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", 
        "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", 
        "minecraft:red_shulker_box", "minecraft:black_shulker_box"
    ],
    "defaultTexture": "minecraft:entity/shulker/shulker",
    "handler": {
        "variables": [
            "type = thisBlock.name.substring(thisBlock.name.indexOf(':') + 1)",
            "type = type.substring(0, type.length() - 11)",
            "type = type.endsWith('_') ? type.substring(0, type.length() - 1) : type",
            "type = type == '' ? 'shulker' : ('shulker_' + type)"
        ],
        "textures": {
            "texture": "'minecraft:entity/shulker/' + type"
        },
        "elements": [
            {
                "from": [ 0, 0, 0 ],
                "to": [ 16, 8, 16 ],
                "entityUVs": [ 0, 7, 16, 13 ],
                "texture": "'#texture'",
                "faces": {
                    "down": {},
                    "east": {},
                    "north": {},
                    "south": {},
                    "west": {}
                }
            },
            {
                "from": [ 0.01, 4, 0.01 ],
                "to": [ 15.99, 16, 15.99 ],
                "entityUVs": [ 0, 0, 16, 7 ],
                "texture": "'#texture'"
            }
        ]
        ...
    }
}

We can add in the variables property to our model part, which is a JSON array of strings where each string is an expression to run. This lets us run code to do calculations. You can also see that the texture texture variable has been updated to now calculate the texture resource identifier based on what type of shulker box it is. To calculate our type, we first want to get rid of the minecraft: namespace by doing type = thisBlock.name.substring(thisBlock.name.indexOf(':') + 1). thisBlock is a special variables that holds information about the current block, like its name, block properties, and position. This expression will find where the : is in the block's name and then set type equal to the name starting from the character just after the :. Then we want to get rid of the shulker_box at the end to get the colour by doing type = type.substring(0, type.length() - 11), which cuts off the last eleven characters of the string stored in type. If there is an underscore at the end, we want to get rid of that as well by doing type = type.endsWith('_') ? type.substring(0, type.length() - 1) : type, which checks if the string stored in type ends with an underscore, and if so cuts it off. Otherwise, it just sets type equal to type which just does nothing.

If the block is minecraft:shulker_box, then the string manipulation so far would cause type to equal "", an empty string. If the block is any of the coloured blocks, then type will contain the colour, like "orange", "light_gray", "red", etc. The texture for the default colour is just called shulker, but the coloured shulker boxes are shulker_<colour>. So, if type is an empty string, then we want "shulker" and otherwise we want "shulker_<colour>", which we do using type = type == '' ? 'shulker' : ('shulker_' + type). Now the different shulker boxes will all get the correct textures.

Lastly, we need to rotate the block. Basically, depending on which way the block is facing, we rotate it by different amounts. We can access block properties in expressions using thisBlock.state.<property>. So, based on the value of the facing property, we can rotate the model on the X and Y axis:

{
    "blocks": [
        "minecraft:shulker_box", "minecraft:white_shulker_box", "minecraft:orange_shulker_box", 
        "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", 
        "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", 
        "minecraft:light_gray_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", 
        "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", 
        "minecraft:red_shulker_box", "minecraft:black_shulker_box"
    ],
    "defaultTexture": "minecraft:entity/shulker/shulker",
    "handler": {
        "variables": [
            "rotX = 0.0",
            "rotY = 0.0",
            "rotX = thisBlock.state.facing == 'down' ? 180.0 : rotX",
            "rotX = thisBlock.state.facing == 'north' ? 90.0 : rotX",
            "rotY = thisBlock.state.facing == 'north' ? 0.0 : rotY",
            "rotX = thisBlock.state.facing == 'east' ? 90.0 : rotX",
            "rotY = thisBlock.state.facing == 'east' ? 90.0 : rotY",
            "rotX = thisBlock.state.facing == 'south' ? 90.0 : rotX",
            "rotY = thisBlock.state.facing == 'south' ? 180.0 : rotY",
            "rotX = thisBlock.state.facing == 'west' ? 90.0 : rotX",
            "rotY = thisBlock.state.facing == 'west' ? 270.0 : rotY",
            "type = thisBlock.name.substring(thisBlock.name.indexOf(':') + 1)",
            "type = type.substring(0, type.length() - 11)",
            "type = type.endsWith('_') ? type.substring(0, type.length() - 1) : type",
            "type = type == '' ? 'shulker' : ('shulker_' + type)"
        ],
        "textures": {
            "texture": "'minecraft:entity/shulker/' + type"
        },
        "transform": {
            "rotate": [ "rotX", "rotY", 0.0 ]
        },
        "elements": [
            {
                "from": [ 0, 0, 0 ],
                "to": [ 16, 8, 16 ],
                "entityUVs": [ 0, 7, 16, 13 ],
                "texture": "'#texture'",
                "faces": {
                    "down": {},
                    "east": {},
                    "north": {},
                    "south": {},
                    "west": {}
                }
            },
            {
                "from": [ 0.01, 4, 0.01 ],
                "to": [ 15.99, 16, 15.99 ],
                "entityUVs": [ 0, 0, 16, 7 ],
                "texture": "'#texture'"
            }
        ]
    }
}

The variables property now has a bunch more expressions in them. We start by setting rotX and rotY equal to zero. Then for each possible facing direction, we check if it is that direction and set rotX and rotY to the correct values for that facing direction. If it's not that direction, then we just set rotX and rotY equal to themselves, which effectively makes that expression do nothing (creating an if statement, basically).

Now that we have our X and Y rotation, we can add in a transform property to our model part to apply that rotation. The X rotation is equal to rotX, the Y rotation is equal to rotY, and the Z rotation is equal to 0.0. All three values are expressions, but when MiEx encounters a number or boolean value, it will interpret that as a hard-coded number or boolean expression. This way you don't need to wrap that into quotation marks.

And this provides the model for shulker boxes.

Painting

Minecraft paintings are set up as entities. MiEx's built-in blockstate format has a variant specifically for entities. The handler part is the same, but the rest is a little bit different. We start off by creating a file called builtins/minecraft/entities/painting.json in a resource pack. In this case, it's in the entities folder to indicate that this is for entities. The namespace folder and file name can be anything. In that file we start out with specifying for which entities this file is:

{
    "entities": [
        "minecraft:painting"
    ],
    "rotX": "0.0",
    "rotY": "0.0",
}

This file is for the entity minecraft:painting. We also specify the rotX and rotY properties. By default, the posX, posY, posZ, rotX (pitch), and rotY (yaw) are read from the entity properties and are used to place the entity where it should be. Since we'll be handling orientation ourselves, we want to set the rotX and rotY values to zero.

Next up is the handler:

{
    "entities": [
        "minecraft:painting"
    ],
    "rotX": "0.0",
    "rotY": "0.0",
    "handler": [
        {
            "transform": {
                "translate": [ -8.0, 0.0, -8.0 ]
            },
            "generators": [
                {
                    "type": "painting",
                    "args": {
                        "properties": "thisEntity.state"
                    }
                }
            ]
        }
    ]
}

We are translating the model by (-8, 0, -8) since regular block models are in the range (0, 0, 0) to (16, 16, 16), but entity models are in the range (-8, 0, -8) to (8, 16, 8), since their position should be at the centre of their feet, so that is where (0, 0, 0) of the model should also be.

Then we have a generator property, which is a JSON array that holds "generators". Generators are functions that generate model data. In this case we want the painting generator and we are providing it with an argument called properties and we give it the entity properties, which is stored in thisEntity.state.

Lastly, we need to rotate the painting based on the facing property stored in the entity data:

{
    "entities": [
        "minecraft:painting"
    ],
    "rotX": "0.0",
    "rotY": "0.0",
    "handler": [
        {
            "variables": [
                "facing = thisEntity.state.Facing != null ? thisEntity.state.Facing : (thisEntity.state.facing != null ? thisEntity.state.facing : (thisEntity.state.Dir != null ? thisEntity.state.Dir : thisEntity.state.Direction))",
                "facing = facing == 0 ? 'south' : (facing == 1 ? 'west' : (facing == 2 ? 'north' : (facing == 3 ? 'south' : (facing == 4 ? 'west' : (facing == 5 ? 'east' : facing)))))",
                "rotX = 0.0",
                "rotY = 0.0",

                "rotX = facing == 'down' ? -90.0 : rotX",

                "rotX = facing == 'up' ? 90.0 : rotX",
                "rotY = facing == 'up' ? 180.0 : rotY",

                "rotY = facing == 'north' ? 180.0 : rotY",

                "rotY = facing == 'east' ? 270.0 : rotY",

                "rotY = facing == 'west' ? 90.0 : rotY"
            ]
        },
        {
            "transform": {
                "translate": [ -8.0, 0.0, -8.0 ],
                "rotate": [ "rotX", "rotY", 0.0 ]
            },
            "generators": [
                {
                    "type": "painting",
                    "args": {
                        "properties": "thisEntity.state"
                    }
                }
            ]
        }
    ]
}

The handler is a JSON array with two model parts in them. The first model part is just used to put in some expressions to calculate the rotation values. The transform of the second model part has been updated to apply that rotation.

Our first expression is a little bit weird. This file needs to support both Minecraft Java Edition and Bedrock Edition paintings, but they use different names for the property and the names have also changed over time. The property name could be Facing, facing, Dir, or Direction. We can check if the entity has a property stored by a given name using thisEntity.state.<property> != null. So, in the first expression we just check for each possible property name if the entity has a property of that name and if so we use that, otherwise we check the next name.

The values of the property could store a string specifying the direction or a number specifying the direction, so if it's a number we want to convert it to a string, which is what we are doing with the second expression. Lastly, based on which way the painting is facing, we can set the rotation values.

Please note the usage of parentheses () in some of these expressions. The expression language is very simple and does not have order of operations, therefore you should always make heavy use parenthesis to ensure that operations are applied in the correct order.

Amendments Water Lily Pad

The Amendments mod allows you to place objects like candles onto lily pads. It does this by removing the lily pad block and swapping the water block below it with a custom amendments:water_lily_pad block, which is waterlogged and keeps a reference of the original lily pad block and its data. Then the candle or other block can be placed above that custom block, where the lily pad used to be. The custom block renders the original block as if it was still above the water, making it look like the original lily pad block and the candle block are occupying the same space. The original block is stored in the block property called Mimic.

This custom block does not have a model file in the mod, instead the mod sets up the model at runtime in its code. So, to add in support for this block, we can make use of MiEx's built-in blockstate file. We can start off by creating a file called builtins/amendments/blockstates/water_lily_pad.json. The namespace folder and file name can be anything. In that file, we start of by specifying which blocks this file is for:

{
    "blocks": [
        "amendments:water_lily_pad"
    ],
    "defaultTexture": "",
    ...
}

This file is for the amendments:water_lily_pad block. We are specifying an empty default texture, which will mean that the block will show up as black in MiEx's viewport, but that's fine.

Now we want to draw the original block, which is stored in the block property Mimic:

{
    "blocks": [
        "amendments:water_lily_pad"
    ],
    "defaultTexture": "",
    "handler": {
        "condition": "thisBlock.state.Mimic != null",
        "transform": {
            "translate": [ 0.0, 15.99, 0.0 ]
        },
        "generators": [
            {
                "type": "block",
                "args": {
                    "name": "thisBlock.state.Mimic.Name",
                    "properties": "thisBlock.state.Mimic.Properties"
                }
            }
        ]
    }
}

We have a handler property with a JSON object which is a "model part". The first thing is that this model part has a condition property which is an expression that needs to evaluate to true for this model part to be included. We are checking here whether the block actually has the Mimic block property. If it doesn't, then we don't want to try and still add in that block.

Then we have a transform which moves the model up by 15.99 units, which is basically one block (one block is sixteen units). It's a little bit less, to prevent z-fighting between this model and the model of the block above it. Then we add in a generator property which is a JSON array of JSON objects where each JSON object is a "generator". Generators are functions that we can call to have it add in model data. In this case we want the block generator, which takes in a block name and block properties and will then look up the block model for that block and add that model to the current model. We then provide is with the block name and properties of the Mimic block, which is our original block.

This all, will cause the block specified by the Mimic block property to be shown above the actual block. Lastly, we need to tag this block as waterlogged so that MiEx will export out the water model where the block is. Otherwise, we would just have a hole in the water surface. We do this by adding the file miex_config.json into the root folder of a resource pack. In that file, we put the following:

{
  "waterlogged.add": [
    "amendments:water_lily_pad"
  ]
}

This adds amendments:water_lily_pad to the list of blocks that should be waterlogged by default. The list itself is called waterlogged, but we are specifying waterlogged.add to indicate that it should be added to the existing list. If we had just specified waterlogged then it would have replaced the entire list with the one that we provide here, which we don't want. waterlogged.remove lets us remove items from a list.

Clone this wiki locally