Road to Modding Hytale

Road to Modding Hytale

Follow me on my journey on how I learned the key components of modding Hytale

February 23, 2026
12 min read
index

Introduction

The best way to learn something is to need it. I’ve noticed this pattern over and over. You can read documentation all day, but until you actually try to build something, you don’t really understand it. A couple days ago I started modding a game called Hytale. I went into it knowing almost nothing. But I had something more valuable than knowledge: I wanted to make something specific work.

Corollary (What is Hytale)

If you do not know what Hytale is, it’s a Minecraft-like game that’s currently in Alpha. It has a modding API that lets you create custom content for the game and the Developer Team is extremely supportive. Click here to learn more about the game.

This is how most good learning happens. You don’t learn to program by reading a book about programming. You learn by trying to make the computer do something and getting stuck. Then you figure out just enough to get unstuck. Then you get stuck again. People talk about “diving deep” like it’s some special technique. But really it just means you kept going when you got stuck. Most people give up at the first obstacle. The ones who succeed are the ones who think “hmm, that’s annoying” and then figure out how to get past it.

I probably spent half my time undoing things I’d just done. This felt inefficient at the time, but looking back, those mistakes taught me more than anything else. When something breaks in a weird way, you have to understand how it works to fix it. There’s a common pattern here. The best way to learn how something works is to break it. Not intentionally, but that’s what ends up happening. You try something, it doesn’t work, and in figuring out why, you learn how the system actually behaves, not just how you thought it behaved. The other thing I noticed is how quickly you can pick things up when you’re motivated. A couple days isn’t very long, but it turns out you can learn a surprising amount in that time if you’re actually building something. Not learning in the sense of reading about it, but learning in the sense of being able to do it. I think this is because real learning is procedural, not declarative. It’s not about knowing facts, it’s about knowing how to do things. And the only way to learn how to do things is to do them.

Tech Stack

The Game Server Architecture is written in Java. I used to think Java was boring. I’d written a few projects in it before, but it never clicked. It felt like unnecessary ceremony. Why write all that boilerplate when you could just write the actual code?

I will still prefer writing in other languages like Go or Python, but I have to admit that Java is pretty good for this kind of thing. The strong typing and object-oriented design make it easier to manage a large codebase. The modding API is also well-designed and easy to use. I was able to get up and running with it pretty quickly, and the documentation is good enough that I didn’t have to spend too much time figuring out how to do things.

Prerequisites

If you want to follow along, you will need to have Java installed on your computer. You will also need to download the Hytale Modding API. You can find instructions for how to do that here. Once you have everything set up, you can start modding!

Core Concepts

Most games use object-oriented programming with inheritance. An Enemy is a Creature, which is an Entity. You build Hierarchy. But Hytale uses a concept called Entity Component System (ECS). The core idea is simple: separate data from behavior. An entity is just an ID. Components hold the data. Systems contain the logic. That’s it.

At first this seems overcomplicated. Why not just have a Player object with health and position? Why split it into separate components? The answer is the same reason Java makes more sense at scale: it prevents problems you don’t see coming. With inheritance, you eventually get stuck. Say you have a Player class and an NPC class. Now you want some NPCs to be controllable by players. Do you make a new ControllableNPC class? Do you refactor Player? The hierarchy fights you. With ECS, you just add the components you need.

The ECS philosophy is “has a” instead of “is a.” A player has a position. Has a health value. Has a network connection. Each piece is independent. You can mix and match them however you want. This makes some things that would be hard in traditional OOP trivial in ECS. Want to add poison to any entity? Make a PoisonComponent. Make a PoisonSystem that processes entities with that component. Done. You didn’t have to touch any existing code.

Store

The Store is where entities live. But entities aren’t stored as complete objects. The data is grouped by archetype. All entities with the same set of components get chunked together. This makes iteration fast. If a system needs to process all entities with Position and Velocity components, the data is already grouped.

Codecs

The Codec system handles saving and loading. Every component needs one. This seems like boilerplate at first. But it means the game can save and load any entity automatically. You don’t have to write save logic for each type. BuilderCodec makes creating Codecs less tedious. You define how each field is serialized. You give it a getter and a setter. The identifier string has to be unique and start with an uppercase letter, which is the kind of arbitrary rule that seems annoying until you realize it prevents name collisions.

Conjecture (Read more about it)

The mentioned concepts are just the tip of the iceberg. There’s a lot more to learn about how ECS works in Hytale, and I’m just scratching the surface. For the purpose of this blog, I only wanted to share some of the key components that I found interesting and useful. If you’re interested in learning more, I highly recommend checking out Hytale Modding documentation including the following resources:

Now let’s build something!

As mentioned before, the best way to learn is to build something. So let’s do that!

The goal:

  • Create a custom item with a custom model and texture
  • Make it craftable in the game
  • Create custom interactions for the item (e.g. right-click to do something)
  • Make the custom data persistant across game sessions

With this goal in mind, we cover the basics for future mods. We learn how to create a custom item, how to make it craftable, how to add interactions, and how to save custom data. This is just the beginning. The possibilities are endless once you understand the core concepts. You can create new mobs, new blocks, new dimensions, new mechanics. The only limit is your imagination (and maybe your coding skills). So let’s get started! Step by Step.

Step 1: Create a Custom Item

Creating a custom item in Hytale isn’t hard, but it’s instructive. You start with a folder structure. This might seem tedious, but folder structures aren’t arbitrary. They’re maps of how the game thinks. The item definition lives on the server side because that’s where truth lives. The visual stuff-icons, models, textures-lives in Common because both client and server need to see it.

The interesting thing about the item definition is how much of it is just data. You’re not writing code to create an item. You’re filling out a form in JSON. This is actually profound. The Hytale developers could have made you write code for everything, but instead they created a declarative system. You say what you want, not how to do it.

Look at the structure:

{
"Id": "My_New_Item",
"Icon": "Icons/ItemsGenerated/my_new_item_icon.png",
"Model": "Items/my_new_item/model.blockymodel",
"Quality": "Common",
"MaxStack": 1
}

That’s it. You describe the item and the system handles the rest. It knows how to load icons, render models, manage stacks. You don’t have to think about any of that.

Step 2: Make it Craftable

Making the item craftable is just as easy. You define the recipe in the JSON file of the item. You specify the ingredients and the type of crafting station needed. The system takes care of the rest. You don’t have to write any code to handle crafting logic. The game knows how to do that for you.

{
..., // other item properties
"Recipe": {
"Input": [
{
"Quantity": 4,
"ItemId": "Ingredient_Bar_Copper"
},
{
"Quantity": 2,
"ItemId": "Ingredient_Bar_Iron"
},
{
"Quantity": 10,
"ResourceTypeId": "Rock"
}
],
"BenchRequirement": [
{
"Type": "Crafting",
"Categories": [
"Workbench_Crafting"
],
"Id": "Workbench"
}
]
},
}

This example shows a recipe that requires 4 copper bars, 2 iron bars, and 10 rocks. It also specifies that the item can only be crafted at a crafting bench inside the category “Crafting”. The system handles the rest, including checking if the player has the required ingredients and if they’re at the right crafting station. Depending on the complexity of your item, you can add more properties to the recipe, change its crafting time, or change the crafting station to a custom one. But for a simple item, this is all you need.

Step 3: Add Interactions

To create a custom interaction, you will need to define the interaction behavior in your plugin code. This typically involves handling events when the item is used by the player. Inherit from SimpleInstantInteraction and override the necessary methods to define what happens when the item is used.

Plugin Code

public class MyCustomInteraction extends SimpleInstantInteraction {
@Override
protected void firstRun(@Nonnull InteractionType interactionType, @Nonnull InteractionContext interactionContext, @Nonnull CooldownHandler cooldownHandler) {
// Custom behavior when the item is used
}
}

What is missing is a custom CODEC to link the interaction to the item.

Note

Each variable you want to store in your component must have its own Codec field defined in the BuilderCodec. For more information on how to create custom Codecs, check out the ECS Codec Guide.

public class MyCustomInteraction extends SimpleInstantInteraction {
public static final BuilderCodec<MyCustomInteraction> CODEC = BuilderCodec.builder(
MyCustomInteraction.class, MyCustomInteraction::new, SimpleInstantInteraction.CODEC
).build();
@Override
protected void firstRun(@Nonnull InteractionType interactionType, @Nonnull InteractionContext interactionContext, @Nonnull CooldownHandler cooldownHandler) {
// Custom behavior when the item is used
}
}

Finally, register the interaction in your plugin’s main class:

public class MyPlugin extends JavaPlugin {
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
this.getCodecRegistry(Interaction.CODEC).register("my_custom_interaction_id", MyCustomInteraction.class, MyCustomInteraction.CODEC);
}
}

This code registers the custom interaction with a unique ID, allowing the game to recognize and use it when any item with with this interaction id is used.

Linking Interaction to Item

To link the custom interaction to the item, add the following property to the item JSON definition:

{
... // existing item properties from above
"Interactions": {
"Secondary": { // depending on the type of interaction you want
"Interactions": [
{
"Type": "my_custom_interaction_id",
}
]
}
}
}

Full Interaction Example

Here is a complete example of a custom interaction that sends a message with the item ID to the player when the item is used:

public class SendMessageInteraction extends SimpleInstantInteraction {
public static final BuilderCodec<SendMessageInteraction> CODEC = BuilderCodec.builder(
SendMessageInteraction.class, SendMessageInteraction::new, SimpleInstantInteraction.CODEC
).build();
public static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Override
protected void firstRun(@Nonnull InteractionType interactionType, @Nonnull InteractionContext interactionContext, @Nonnull CooldownHandler cooldownHandler) {
CommandBuffer<EntityStore> commandBuffer = interactionContext.getCommandBuffer();
if (commandBuffer == null) {
interactionContext.getState().state = InteractionState.Failed;
LOGGER.atInfo().log("CommandBuffer is null");
return;
}
World world = commandBuffer.getExternalData().getWorld(); // just to show how to get the world if needed
Store<EntityStore> store = commandBuffer.getExternalData().getStore(); // just to show how to get the store if needed
Ref<EntityStore> ref = interactionContext.getEntity();
Player player = commandBuffer.getComponent(ref, Player.getComponentType());
if (player == null) {
interactionContext.getState().state = InteractionState.Failed;
LOGGER.atInfo().log("Player is null");
return;
}
ItemStack itemStack = interactionContext.getHeldItem();
if (itemStack == null) {
interactionContext.getState().state = InteractionState.Failed;
LOGGER.atInfo().log("ItemStack is null");
return;
}
player.sendMessage(Message.raw("You have used the custom item +" + itemStack.getItemId()));
}
}

Advanced Interaction Features

Interactions are highly flexible. Since they are nested, you can combine multiple interactions to create complex behaviors. For example, you could have an interaction that first checks certain conditions (like player health or environment) before executing another interaction. Afterwards, you can chain interactions to create a sequence of actions triggered by a single item use. Or you could create interactions that need to be charged over time before they activate.

To get your creative juices flowing, here are some examples of interaction types:

  1. Condition: Check specific conditions before allowing the interaction to proceed (e.g., only if player crouches):
{
"Type": "Condition",
"Crouching": true, // only allow if player is crouching example
"Failed": "Block_Secondary",
"Next": {
... // next interaction to run if condition is met
}
}
  1. Charge: Require the player to hold the interaction for a certain duration before it activates (This behavior for example is called if the player eats food):
{
"Type": "Charging",
"FailsOnDamage": true, // cancel if player takes damage
"HorizontalSpeedMultiplier": 0.4, // reduce speed while charging
"Next": {
"2.5" : { // interaction after 2.5 seconds of charging
... // interaction to run after charge time
},
},
"Failed": {
... // interaction to run if charging fails
}
}
  1. Serial: Execute a series of interactions in order, one after the other:
{
"Type": "Serial",
"Interactions": [
{
... // first interaction
},
{
... // second interaction
}
]
}
  1. Replace: Replace the default behavior of the item (e.g., inherited from parent) with a custom interaction:
{
"Type": "Replace",
"Var": "Item_Default_Interaction",
"DefaultValue": {
"Interactions": [
{
... // default interaction that replaces the original
}
]
}
}
  1. Simple: Simple Interaction that performs a single action, such as sending a message or applying an effect:
{
"Type": "Simple"
}

Advanced Interaction Example

Here is an example of a more complex interaction that requires the player to crouch and charge for 2.5 seconds before executing the custom action:

{
... // existing item properties from above
"Interactions": {
"Secondary": {
"Interactions": [
{
"Type": "Condition",
"Crouching": true,
"Failed": "Block_Secondary",
"Next": {
"Type": "Charging",
"FailsOnDamage": true,
"HorizontalSpeedMultiplier": 0.5,
"Next": {
"2.5": {
"Type": "my_custom_interaction_id",
}
},
"Failed": {
"Type": "Simple"
}
}
}
]
}
}
}

Conclusion

With this knowledge, you can create a wide variety of custom items and interactions in Hytale. The ECS architecture allows for flexibility and scalability, making it easier to manage complex behaviors. By understanding the core concepts and following the steps outlined in this guide, you can start building your own mods and contribute to the Hytale community.