Skip to main content
This guide shows how to create a complete Fabric server-side mod that integrates with BetterHud, including custom placeholders, event triggers, and popup management.

Example Repository

For a complete working example, see the betterhud-fabric-example mod.

Build Configuration

Gradle (Kotlin DSL)

build.gradle.kts
plugins {
    id("fabric-loom") version "1.9.+"
    kotlin("jvm") version "2.1.0"
}

repositories {
    mavenCentral()
    maven("https://maven.fabricmc.net/")
}

val minecraftVersion = "1.21.4"
val fabricLoaderVersion = "0.16.10"
val fabricApiVersion = "0.114.0+1.21.4"

dependencies {
    minecraft("com.mojang:minecraft:${minecraftVersion}")
    mappings("net.fabricmc:yarn:${minecraftVersion}+build.1:v2")
    modImplementation("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
    modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricApiVersion}")
    
    // BetterHud dependencies
    modCompileOnly("io.github.toxicity188:BetterHud-fabric-api:VERSION")
    compileOnly("io.github.toxicity188:BetterHud-standard-api:VERSION")
    compileOnly("io.github.toxicity188:BetterCommand:VERSION")
}

Gradle (Groovy)

build.gradle
plugins {
    id 'fabric-loom' version '1.9.+'
    id 'java'
}

repositories {
    mavenCentral()
    maven { url 'https://maven.fabricmc.net/' }
}

dependencies {
    minecraft 'com.mojang:minecraft:1.21.4'
    mappings 'net.fabricmc:yarn:1.21.4+build.1:v2'
    modImplementation 'net.fabricmc:fabric-loader:0.16.10'
    modImplementation 'net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4'
    
    modCompileOnly 'io.github.toxicity188:BetterHud-fabric-api:VERSION'
    compileOnly 'io.github.toxicity188:BetterHud-standard-api:VERSION'
    compileOnly 'io.github.toxicity188:BetterCommand:VERSION'
}

Mod Setup

fabric.mod.json

fabric.mod.json
{
  "schemaVersion": 1,
  "id": "betterhud-example",
  "version": "1.0.0",
  "name": "BetterHud Example",
  "description": "Example integration with BetterHud",
  "authors": ["YourName"],
  "contact": {},
  "license": "MIT",
  "icon": "assets/betterhud-example/icon.png",
  "environment": "server",
  "entrypoints": {
    "main": [
      "com.example.betterhudexample.BetterHudExample"
    ]
  },
  "depends": {
    "fabricloader": ">=0.16.0",
    "fabric-api": "*",
    "minecraft": ">=1.21",
    "betterhud": "*"
  }
}

Main Mod Class

BetterHudExample.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.fabric.FabricBootstrap;
import kr.toxicity.hud.api.fabric.event.EventRegistry;
import kr.toxicity.hud.api.manager.PlaceholderManager;
import kr.toxicity.hud.api.placeholder.HudPlaceholder;
import kr.toxicity.hud.api.plugin.ReloadState;
import net.fabricmc.api.ModInitializer;
import net.minecraft.server.network.ServerPlayerEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BetterHudExample implements ModInitializer {
    
    public static final String MOD_ID = "betterhud-example";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
    
    private static BetterHudExample instance;
    
    @Override
    public void onInitialize() {
        instance = this;
        
        LOGGER.info("Initializing BetterHud Example Mod");
        
        try {
            // Wait for BetterHud to load, then register our components
            FabricBootstrap.POST_RELOAD_EVENT.register(this::onBetterHudReload);
            
            // Register custom events
            CustomEvents.register();
            
            LOGGER.info("BetterHud Example initialized successfully!");
            
        } catch (Exception e) {
            LOGGER.error("Failed to initialize BetterHud integration", e);
        }
    }
    
    private void onBetterHudReload(ReloadState state) {
        if (state.isSuccess()) {
            LOGGER.info("BetterHud reloaded, registering placeholders...");
            registerPlaceholders();
            registerTriggers();
        } else {
            LOGGER.warn("BetterHud reload failed: " + state.getMessage());
        }
    }
    
    private void registerPlaceholders() {
        PlaceholderManager manager = BetterHudAPI.inst().getPlaceholderManager();
        
        // Register number placeholder - player experience level
        HudPlaceholder.<Number>builder()
            .function((args, event) -> player -> {
                ServerPlayerEntity serverPlayer = 
                    (ServerPlayerEntity) player.handle();
                return serverPlayer.experienceLevel;
            })
            .build()
            .add("player_exp_level", manager.getNumberContainer());
        
        // Register string placeholder - current dimension
        HudPlaceholder.<String>builder()
            .function((args, event) -> player -> {
                ServerPlayerEntity serverPlayer = 
                    (ServerPlayerEntity) player.handle();
                return serverPlayer.getWorld().getRegistryKey().getValue().toString();
            })
            .build()
            .add("player_dimension", manager.getStringContainer());
        
        // Register boolean placeholder - is in nether
        HudPlaceholder.<Boolean>builder()
            .function((args, event) -> player -> {
                ServerPlayerEntity serverPlayer = 
                    (ServerPlayerEntity) player.handle();
                return serverPlayer.getWorld().getRegistryKey().getValue()
                    .getPath().equals("the_nether");
            })
            .build()
            .add("in_nether", manager.getBooleanContainer());
        
        LOGGER.info("Registered custom placeholders");
    }
    
    private void registerTriggers() {
        // Register custom event triggers
        CustomTriggers.register();
        LOGGER.info("Registered custom triggers");
    }
    
    public static BetterHudExample getInstance() {
        return instance;
    }
}

Custom Fabric Events

Defining Custom Events

CustomEvents.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.fabric.event.EventRegistry;
import kr.toxicity.hud.api.fabric.event.PlayerEvent;
import net.minecraft.server.network.ServerPlayerEntity;
import org.jetbrains.annotations.NotNull;

import java.util.UUID;

public class CustomEvents {
    
    // Custom player skill event
    public static final EventRegistry<SkillUseEvent> SKILL_USE = new EventRegistry<>();
    
    // Custom player achievement event
    public static final EventRegistry<CustomAchievementEvent> CUSTOM_ACHIEVEMENT = new EventRegistry<>();
    
    public static void register() {
        // Events are registered by creating the EventRegistry instances above
        BetterHudExample.LOGGER.info("Custom events registered");
    }
    
    // Custom event class for skill usage
    public static class SkillUseEvent implements PlayerEvent {
        private final ServerPlayerEntity player;
        private final String skillName;
        private final int skillLevel;
        
        public SkillUseEvent(ServerPlayerEntity player, String skillName, int skillLevel) {
            this.player = player;
            this.skillName = skillName;
            this.skillLevel = skillLevel;
        }
        
        @Override
        public @NotNull UUID getPlayerUUID() {
            return player.getUuid();
        }
        
        public ServerPlayerEntity getPlayer() {
            return player;
        }
        
        public String getSkillName() {
            return skillName;
        }
        
        public int getSkillLevel() {
            return skillLevel;
        }
    }
    
    // Custom achievement event
    public static class CustomAchievementEvent implements PlayerEvent {
        private final ServerPlayerEntity player;
        private final String achievementId;
        private final int points;
        
        public CustomAchievementEvent(ServerPlayerEntity player, String achievementId, int points) {
            this.player = player;
            this.achievementId = achievementId;
            this.points = points;
        }
        
        @Override
        public @NotNull UUID getPlayerUUID() {
            return player.getUuid();
        }
        
        public ServerPlayerEntity getPlayer() {
            return player;
        }
        
        public String getAchievementId() {
            return achievementId;
        }
        
        public int getPoints() {
            return points;
        }
    }
}

Custom Triggers

CustomTriggers.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.fabric.trigger.HudFabricEventTrigger;
import kr.toxicity.hud.api.manager.TriggerManager;
import kr.toxicity.hud.api.update.UpdateEvent;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

public class CustomTriggers {
    
    public static void register() {
        TriggerManager manager = BetterHudAPI.inst().getTriggerManager();
        
        // Register skill use trigger
        manager.addTrigger("skill_use", new HudFabricEventTrigger<>() {
            @Override
            public @NotNull Object getKey(CustomEvents.SkillUseEvent event) {
                return event.getPlayerUUID();
            }
            
            @Override
            public @NotNull EventRegistry<CustomEvents.SkillUseEvent> registry() {
                return CustomEvents.SKILL_USE;
            }
        });
        
        // Register achievement trigger with custom variables
        manager.addTrigger("custom_achievement", new HudFabricEventTrigger<>() {
            @Override
            public @NotNull Object getKey(CustomEvents.CustomAchievementEvent event) {
                return event.getPlayerUUID();
            }
            
            @Override
            public @NotNull EventRegistry<CustomEvents.CustomAchievementEvent> registry() {
                return CustomEvents.CUSTOM_ACHIEVEMENT;
            }
        });
    }
}

Triggering Custom Events

SkillSystem.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.manager.PlayerManager;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.update.UpdateEvent;
import net.minecraft.server.network.ServerPlayerEntity;

import java.util.HashMap;
import java.util.Map;

public class SkillSystem {
    
    public static void useSkill(ServerPlayerEntity player, String skillName, int level) {
        // Create and invoke the custom event
        CustomEvents.SkillUseEvent event = new CustomEvents.SkillUseEvent(
            player, 
            skillName, 
            level
        );
        
        // Trigger the event through the EventRegistry
        CustomEvents.SKILL_USE.invoke(event);
        
        // Also show a popup with skill info
        showSkillPopup(player, skillName, level);
    }
    
    private static void showSkillPopup(ServerPlayerEntity player, String skillName, int level) {
        PlayerManager playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUuid());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        var popup = popupManager.getPopup("skill_use_popup");
        
        if (popup != null) {
            // Create variables for the popup
            Map<String, String> variables = new HashMap<>();
            variables.put("skill_name", skillName);
            variables.put("skill_level", String.valueOf(level));
            
            // Create update event with variables
            UpdateEvent updateEvent = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            try {
                popup.show(hudPlayer, updateEvent);
            } catch (Exception e) {
                BetterHudExample.LOGGER.error("Failed to show skill popup", e);
            }
        }
    }
    
    public static void grantAchievement(ServerPlayerEntity player, String achievementId, int points) {
        // Create achievement event
        CustomEvents.CustomAchievementEvent event = new CustomEvents.CustomAchievementEvent(
            player,
            achievementId,
            points
        );
        
        // Invoke the event
        CustomEvents.CUSTOM_ACHIEVEMENT.invoke(event);
        
        BetterHudExample.LOGGER.info("Player {} earned achievement: {}", 
            player.getName().getString(), achievementId);
    }
}

Working with Players

FabricPlayerHelper.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.manager.PlayerManager;
import kr.toxicity.hud.api.player.HudPlayer;
import net.minecraft.server.network.ServerPlayerEntity;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class FabricPlayerHelper {
    
    @Nullable
    public static HudPlayer getHudPlayer(ServerPlayerEntity player) {
        PlayerManager manager = BetterHudAPI.inst().getPlayerManager();
        return manager.getHudPlayer(player.getUuid());
    }
    
    @Nullable
    public static HudPlayer getHudPlayer(UUID uuid) {
        PlayerManager manager = BetterHudAPI.inst().getPlayerManager();
        return manager.getHudPlayer(uuid);
    }
    
    public static void disableHud(ServerPlayerEntity player) {
        HudPlayer hudPlayer = getHudPlayer(player);
        if (hudPlayer != null) {
            hudPlayer.setHudEnabled(false);
        }
    }
    
    public static void enableHud(ServerPlayerEntity player) {
        HudPlayer hudPlayer = getHudPlayer(player);
        if (hudPlayer != null) {
            hudPlayer.setHudEnabled(true);
        }
    }
    
    public static void updatePlayerVariable(ServerPlayerEntity player, String key, String value) {
        HudPlayer hudPlayer = getHudPlayer(player);
        if (hudPlayer != null) {
            hudPlayer.getVariableMap().put(key, value);
        }
    }
}

Advanced Placeholder Examples

AdvancedPlaceholders.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.placeholder.HudPlaceholder;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.stat.Stats;
import net.minecraft.util.math.BlockPos;

public class AdvancedPlaceholders {
    
    public static void register() {
        var manager = BetterHudAPI.inst().getPlaceholderManager();
        
        // Placeholder with arguments - player stat
        HudPlaceholder.<Number>builder()
            .requiredArgsLength(1)
            .function((args, event) -> {
                String statType = args.get(0);
                
                return player -> {
                    ServerPlayerEntity serverPlayer = (ServerPlayerEntity) player.handle();
                    
                    return switch (statType.toLowerCase()) {
                        case "deaths" -> serverPlayer.getStatHandler()
                            .getStat(Stats.CUSTOM.getOrCreateStat(Stats.DEATHS));
                        case "jumps" -> serverPlayer.getStatHandler()
                            .getStat(Stats.CUSTOM.getOrCreateStat(Stats.JUMP));
                        case "playtime" -> serverPlayer.getStatHandler()
                            .getStat(Stats.CUSTOM.getOrCreateStat(Stats.PLAY_TIME)) / 20;
                        default -> 0;
                    };
                };
            })
            .build()
            .add("player_stat", manager.getNumberContainer());
        
        // Player coordinates
        HudPlaceholder.<Number>builder()
            .requiredArgsLength(1)
            .function((args, event) -> {
                String coordinate = args.get(0);
                
                return player -> {
                    ServerPlayerEntity serverPlayer = (ServerPlayerEntity) player.handle();
                    BlockPos pos = serverPlayer.getBlockPos();
                    
                    return switch (coordinate.toLowerCase()) {
                        case "x" -> pos.getX();
                        case "y" -> pos.getY();
                        case "z" -> pos.getZ();
                        default -> 0;
                    };
                };
            })
            .build()
            .add("player_coord", manager.getNumberContainer());
        
        // Time of day
        HudPlaceholder.<String>builder()
            .function((args, event) -> player -> {
                ServerPlayerEntity serverPlayer = (ServerPlayerEntity) player.handle();
                long timeOfDay = serverPlayer.getWorld().getTimeOfDay() % 24000;
                
                if (timeOfDay < 6000) return "morning";
                else if (timeOfDay < 12000) return "day";
                else if (timeOfDay < 18000) return "evening";
                else return "night";
            })
            .build()
            .add("time_of_day", manager.getStringContainer());
    }
}

Compass Integration

FabricCompassHelper.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.adapter.LocationWrapper;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.player.PointedLocation;
import kr.toxicity.hud.api.player.PointedLocationProvider;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

import java.util.HashSet;
import java.util.Set;

public class FabricCompassHelper {
    
    public static void addCompassPoint(
        ServerPlayerEntity player, 
        BlockPos pos, 
        String name, 
        String icon
    ) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUuid());
        
        if (hudPlayer != null) {
            LocationWrapper location = LocationWrapper.of(
                player.getWorld().getRegistryKey().getValue().toString(),
                pos.getX(),
                pos.getY(),
                pos.getZ()
            );
            
            PointedLocation pointedLocation = PointedLocation.builder()
                .location(location)
                .name(name)
                .icon(icon)
                .build();
            
            hudPlayer.pointers().add(pointedLocation);
        }
    }
    
    public static void registerWorldSpawnProvider() {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        
        playerManager.addLocationProvider(new PointedLocationProvider() {
            @Override
            public Set<PointedLocation> provide(HudPlayer player) {
                Set<PointedLocation> locations = new HashSet<>();
                
                ServerPlayerEntity serverPlayer = (ServerPlayerEntity) player.handle();
                BlockPos spawnPos = serverPlayer.getWorld().getSpawnPos();
                
                locations.add(PointedLocation.builder()
                    .location(LocationWrapper.of(
                        serverPlayer.getWorld().getRegistryKey().getValue().toString(),
                        spawnPos.getX(),
                        spawnPos.getY(),
                        spawnPos.getZ()
                    ))
                    .name("World Spawn")
                    .icon("spawn")
                    .build());
                
                return locations;
            }
        });
    }
}

Error Handling

SafeFabricOperations.java
package com.example.betterhudexample;

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.update.UpdateEvent;
import net.minecraft.server.network.ServerPlayerEntity;

import java.util.HashMap;

public class SafeFabricOperations {
    
    public static void safeShowPopup(
        ServerPlayerEntity player, 
        String popupName, 
        HashMap<String, String> variables
    ) {
        try {
            var playerManager = BetterHudAPI.inst().getPlayerManager();
            var hudPlayer = playerManager.getHudPlayer(player.getUuid());
            
            if (hudPlayer == null) {
                BetterHudExample.LOGGER.warn(
                    "HudPlayer not found for {}", 
                    player.getName().getString()
                );
                return;
            }
            
            var popupManager = BetterHudAPI.inst().getPopupManager();
            var popup = popupManager.getPopup(popupName);
            
            if (popup == null) {
                BetterHudExample.LOGGER.warn("Popup '{}' not found!", popupName);
                return;
            }
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables != null ? variables : new HashMap<>())
                .build();
            
            popup.show(hudPlayer, event);
            
        } catch (Exception e) {
            BetterHudExample.LOGGER.error(
                "Failed to show popup to {}", 
                player.getName().getString(), 
                e
            );
        }
    }
}

Next Steps

Custom HUD

Learn how to create custom HUD elements

Custom Popup

Create dynamic popup notifications

Fabric API Reference

Complete Fabric API documentation

Standard API

Core API reference

Build docs developers (and LLMs) love