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