15 min

User Interface (UI)

Create custom pages, HUDs, windows and notifications. Complete guide to the Hytale UI system.

Experimental

This documentation is based on reverse-engineering. Some features may not work as described. We're actively testing and updating this page.

critical: manifest.json
Your plugin's manifest.json MUST include "IncludesAssetPack": truefor the game to find your .ui files. Without this, you'll get "Could not find document" errors even if your files are correctly placed.

Project Structure

UI files must be placed in Common/UI/Custom/Pages/ inside your JAR:

1my-plugin.jar
2├── manifest.json # MUST have "IncludesAssetPack": true
3├── Common/
4│ └── UI/
5│ └── Custom/
6│ └── Pages/
7│ └── MyMod_Page.ui # Prefix with unique identifier
8└── com/
9 └── example/
10 └── myplugin/
11 ├── MyPlugin.java
12 └── MyUIPage.java

manifest.json (Required Fields)

manifest.jsonjava
1{
2 "Group": "com.example",
3 "Name": "my-plugin",
4 "Version": "1.0.0",
5 "Main": "com.example.myplugin.MyPlugin",
6 "IncludesAssetPack": true
7}
includesassetpack
This field tells the game to look for assets (UI files, textures, etc.) inside your JAR. Without it, only Java classes are loaded.

UI File Path in Code

When referencing UI files in Java, use path relative to Common/UI/Custom/:

1// File location: Common/UI/Custom/Pages/MyMod_Page.ui
2// Java reference:
3builder.append("Pages/MyMod_Page.ui"); // ✓ Correct
4
5// NOT these:
6builder.append("Common/UI/Custom/Pages/MyMod_Page.ui"); // ✗ Wrong
7builder.append("Custom/Pages/MyMod_Page.ui"); // ✗ Wrong
8builder.append("MyMod_Page.ui"); // ✗ Wrong

Architecture Overview

Hytale's UI system is composed of three managers, each handling a different type of interface:

managerpurposeexamples
PageManagerFull-screen menus, dialogsShop, respawn screen, NPC dialog
HudManagerPersistent on-screen elementsHealth bar, custom boss bar, quest tracker
WindowManagerInventory-based interfacesContainers, crafting benches
1// Access managers from Player
2Player player = ctx.senderAs(Player.class);
3
4PageManager pageManager = player.getPageManager();
5HudManager hudManager = player.getHudManager();
6WindowManager windowManager = player.getWindowManager();

Custom Pages

Pages are full-screen interfaces that can be dismissed by the player. There are two types: BasicCustomUIPage (display only) andInteractiveCustomUIPage (with event handling).

BasicCustomUIPage

For simple display-only pages without interaction:

WelcomePage.javajava
1import com.hypixel.hytale.server.core.entity.entities.player.pages.BasicCustomUIPage;
2import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
3import com.hypixel.hytale.server.core.universe.PlayerRef;
4import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
5
6public class WelcomePage extends BasicCustomUIPage {
7
8 private final String playerName;
9
10 public WelcomePage(PlayerRef playerRef, String playerName) {
11 super(playerRef, CustomPageLifetime.CanDismiss);
12 this.playerName = playerName;
13 }
14
15 @Override
16 public void build(UICommandBuilder commands) {
17 // Path relative to Common/UI/Custom/
18 commands.append("Pages/MyMod_WelcomePage.ui");
19
20 // Set dynamic values using element selectors
21 commands.set("#Title.Text", "Welcome!");
22 commands.set("#PlayerName.Text", playerName);
23 commands.set("#Badge.Visible", true);
24 }
25}

UI File Syntax (.ui)

The .ui format is Hytale's declarative UI language:

MyMod_WelcomePage.uijava
1$C = "../Common.ui";
2
3$C.@PageOverlay {
4
5 $C.@Container {
6 Anchor: (Width: 600, Height: 400);
7
8 #Title {
9 Group {
10 $C.@Title {
11 @Text = "Welcome Page";
12 }
13 }
14 }
15
16 #Content {
17 LayoutMode: Top;
18 Padding: (Full: 20);
19
20 Label #PlayerName {
21 Style: (FontSize: 18, TextColor: #ffffff);
22 Anchor: (Bottom: 10);
23 }
24
25 Group #Badge {
26 Visible: false;
27 // Badge content...
28 }
29
30 Button #CloseButton {
31 Anchor: (Width: 200, Height: 50);
32 Background: (Color: #d4a054);
33 Style: (
34 Hovered: (Background: #e8b868),
35 Pressed: (Background: #a67c3d)
36 );
37
38 Label {
39 Style: (FontSize: 14, RenderBold: true, TextColor: #000000);
40 Text: "Close";
41 }
42 }
43 }
44 }
45}
46
47$C.@BackButton {}
syntaxmeaning
$C = "../Common.ui"Import base components from Common.ui
$C.@PageOverlayUse PageOverlay template from Common.ui
$C.@ContainerUse Container template
#ElementIdElement ID for targeting in Java code
Anchor: (Width: X, Height: Y)Size constraints
LayoutMode: TopStack children vertically from top
Padding: (Full: 20)20px padding on all sides
Style: (...)Visual styling properties
Visible: falseHide element by default

InteractiveCustomUIPage

For pages that respond to user input (buttons, clicks, etc.):

ShopPage.javajava
1import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
2import com.hypixel.hytale.server.core.ui.builder.*;
3import com.hypixel.hytale.protocol.packets.interface_.*;
4import com.hypixel.hytale.codec.builder.BuilderCodec;
5import com.hypixel.hytale.codec.KeyedCodec;
6import com.hypixel.hytale.codec.Codec;
7
8public class ShopPage extends InteractiveCustomUIPage {
9
10 private int playerCoins;
11
12 public ShopPage(PlayerRef playerRef, int coins) {
13 super(playerRef, CustomPageLifetime.CanDismiss, ShopEventData.CODEC);
14 this.playerCoins = coins;
15 }
16
17 @Override
18 public void build(
19 Ref ref,
20 UICommandBuilder commands,
21 UIEventBuilder events,
22 Store store
23 ) {
24 commands.append("Pages/ShopPage.ui");
25 commands.set("#coins-display", playerCoins + " coins");
26
27 // Bind button click event
28 events.addEventBinding(
29 CustomUIEventBindingType.Activating,
30 "#buy-button",
31 EventData.of("Action", "buy").append("ItemId", "sword_01")
32 );
33
34 // Bind close button
35 events.addEventBinding(
36 CustomUIEventBindingType.Activating,
37 "#close-button",
38 EventData.of("Action", "close")
39 );
40 }
41
42 @Override
43 public void handleDataEvent(
44 Ref ref,
45 Store store,
46 ShopEventData data
47 ) {
48 if ("buy".equals(data.action)) {
49 // Handle purchase
50 playerCoins -= 100;
51
52 // Update UI dynamically
53 UICommandBuilder update = new UICommandBuilder();
54 update.set("#coins-display", playerCoins + " coins");
55 // sendUpdate(update); // Send update to client
56 } else if ("close".equals(data.action)) {
57 // Close the page
58 // close();
59 }
60 }
61
62 // Event data codec for deserializing client events
63 public static class ShopEventData {
64 public static final BuilderCodec CODEC =
65 BuilderCodec.builder(ShopEventData.class, ShopEventData::new)
66 .append(new KeyedCodec<>("Action", Codec.STRING),
67 (d, v) -> d.action = v, d -> d.action).add()
68 .append(new KeyedCodec<>("ItemId", Codec.STRING),
69 (d, v) -> d.itemId = v, d -> d.itemId).add()
70 .build();
71
72 public String action;
73 public String itemId;
74 }
75}

Opening Pages

1// Must run on world thread!
2player.getWorld().execute(() -> {
3 // Get required references
4 Ref entityRef = player.getReference();
5 Store store = player.getWorld().getEntityStore().getStore();
6
7 // Create and open page
8 WelcomePage page = new WelcomePage(player.getPlayerRef(), player.getName());
9 player.getPageManager().openCustomPage(entityRef, store, page);
10});
11
12// Open a built-in page
13player.getPageManager().setPage(entityRef, store, Page.Inventory);
14
15// Open page with windows (e.g., container + custom UI)
16player.getPageManager().openCustomPageWithWindows(entityRef, store, myPage, myWindow);
world thread required
openCustomPage() must be called within world.execute(). Calling from wrong thread will cause issues.

Page Lifetime

lifetimebehavior
CantClosePlayer cannot close the page (forced display)
CanDismissPlayer can press ESC or click outside to close
CanDismissOrCloseThroughInteractionCan be closed by ESC or by clicking a close button

UI Builders

UICommandBuilder

Used to manipulate UI structure and set values:

1UICommandBuilder commands = new UICommandBuilder();
2
3// Layout commands - load/modify UI structure
4commands.append("Pages/MyPage.ui"); // Load UI document
5commands.append("#container", "Components/Item.ui"); // Append to element
6commands.appendInline("#container", "
"); // Append inline markup
7commands.insertBefore("#target", "Components/Header.ui");
8commands.remove("#old-element"); // Remove element
9commands.clear("#container"); // Clear all children
10
11// Value commands - set element values
12commands.set("#text", "Hello World"); // String
13commands.set("#count", 42); // int
14commands.set("#progress", 0.75f); // float
15commands.set("#enabled", true); // boolean
16commands.setNull("#optional"); // null value
17commands.setObject("#complex", myObject); // Object (needs codec)

UIEventBuilder

Used to bind UI events to callbacks:

1UIEventBuilder events = new UIEventBuilder();
2
3// Simple event binding
4events.addEventBinding(
5 CustomUIEventBindingType.Activating, // Event type
6 "#my-button" // Element selector
7);
8
9// Event with data
10events.addEventBinding(
11 CustomUIEventBindingType.Activating,
12 "#buy-btn",
13 EventData.of("Action", "buy").append("Id", "123")
14);
15
16// Non-locking event (UI stays responsive while processing)
17events.addEventBinding(
18 CustomUIEventBindingType.ValueChanged,
19 "#slider",
20 EventData.of("Type", "volume"),
21 false // locksInterface = false
22);

EventData

1// Create event data (key-value pairs)
2EventData data = new EventData();
3data = data.append("Action", "submit");
4data = data.append("Quantity", "5"); // Note: values are strings
5
6// Or use static factory
7EventData data = EventData.of("Action", "buy")
8 .append("ItemId", "sword")
9 .append("Price", "100");
keys must start with uppercase
All KeyedCodec and EventData keys MUST start with an uppercase letter. Use "Action" not "action", "ItemId" not "itemId".
string values
All EventData values must be strings. Numbers should be converted:.append("Count", String.valueOf(42))

UI Event Types

Available event types in CustomUIEventBindingType:

eventtrigger
ActivatingPrimary click/activation
RightClickingRight mouse button
DoubleClickingDouble click
MouseEnteredMouse enters element
MouseExitedMouse leaves element
ValueChangedInput value changed (sliders, text)
FocusGainedElement receives focus
FocusLostElement loses focus
KeyDownKey pressed while focused
DismissingPage is being dismissed
ValidatingForm validation triggered

Slot Events (for grids/inventories)

eventtrigger
SlotClickingClick on inventory slot
SlotDoubleClickingDouble-click on slot
SlotMouseEnteredMouse enters slot
SlotMouseExitedMouse leaves slot
SlotMouseDragCompletedDrag operation completed
SlotMouseDragExitedDrag left the slot area
SlotClickReleaseWhileDraggingReleased click while dragging
SlotClickPressWhileDraggingPressed click while dragging

Other Events

eventtrigger
ElementReorderedList element was reordered
DragCancelledDrag operation cancelled
DroppedItem dropped on element
SelectedTabChangedTab selection changed
MouseButtonReleasedAny mouse button released

Custom HUD

HUD elements are persistent on-screen displays that don't block gameplay.

Creating Custom HUD

BossHealthHud.javajava
1import com.hypixel.hytale.server.core.entity.entities.player.hud.CustomUIHud;
2import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
3
4public class BossHealthHud extends CustomUIHud {
5
6 private String bossName;
7 private float healthPercent;
8
9 public BossHealthHud(PlayerRef playerRef, String bossName) {
10 super(playerRef);
11 this.bossName = bossName;
12 this.healthPercent = 1.0f;
13 }
14
15 // Called when HUD is first shown
16 // Override the build method to set up initial UI
17
18 public void updateHealth(float percent) {
19 this.healthPercent = percent;
20
21 UICommandBuilder update = new UICommandBuilder();
22 update.set("#health-fill", healthPercent);
23 update.set("#health-text", Math.round(healthPercent * 100) + "%");
24
25 // false = don't clear existing HUD, just update
26 update(false, update);
27 }
28}

HudManager Methods

1HudManager hudManager = playerComponent.getHudManager();
2
3// Set custom HUD (replaces any existing)
4hudManager.setCustomHud(playerRef, new BossHealthHud(playerRef, "Dragon"));
5
6// Remove custom HUD
7hudManager.setCustomHud(playerRef, null);
8
9// Get current custom HUD
10CustomUIHud currentHud = hudManager.getCustomHud();
11
12// Reset to default HUD state
13hudManager.resetHud(playerRef);
14
15// Reset entire UI state
16hudManager.resetUserInterface(playerRef);

Built-in HUD Components

Control visibility of built-in HUD elements:

1// Show only specific components
2hudManager.setVisibleHudComponents(playerRef,
3 HudComponent.Hotbar,
4 HudComponent.Health,
5 HudComponent.Chat,
6 HudComponent.Reticle
7);
8
9// Show additional components
10hudManager.showHudComponents(playerRef,
11 HudComponent.Compass,
12 HudComponent.Stamina
13);
14
15// Hide specific components
16hudManager.hideHudComponents(playerRef,
17 HudComponent.KillFeed,
18 HudComponent.PlayerList
19);

HudComponent Enum

componentdescription
HotbarBottom action bar
HealthHealth display
ManaMana display
StaminaStamina bar
OxygenUnderwater oxygen
SleepSleep indicator
ReticleCrosshair/cursor
ChatChat window
NotificationsToast notifications
KillFeedKill/death feed
PlayerListTab player list
EventTitleTitle/subtitle display
CompassDirection compass
ObjectivePanelQuest/objective display
PortalPanelPortal information
StatusIconsStatus effect icons
InputBindingsControl hints
RequestsRequest notifications
SpeedometerVehicle speed
AmmoIndicatorWeapon ammo
UtilitySlotSelectorUtility item selector
BlockVariantSelectorBlock variant picker
BuilderToolsLegendBuilder mode legend
BuilderToolsMaterialSlotSelectorBuilder material selector

Windows (Containers)

Windows are used for inventory-based interfaces like containers and crafting.

WindowManager Methods

1WindowManager windowManager = playerComponent.getWindowManager();
2
3// Open a window
4windowManager.openWindow(myWindow);
5
6// Open multiple windows
7windowManager.openWindows(window1, window2);
8
9// Get window by ID
10Window window = windowManager.getWindow(windowId);
11
12// Get all open windows
13List windows = windowManager.getWindows();
14
15// Update window data
16windowManager.updateWindow(myWindow);
17
18// Close window by ID
19windowManager.closeWindow(windowId);
20
21// Close all windows
22windowManager.closeAllWindows();

Window Types

windowtypepurpose
ContainerGeneric item container (chest, etc.)
PocketCraftingQuick crafting (2x2)
BasicCraftingStandard crafting table
DiagramCraftingPattern-based crafting
StructuralCraftingBuilding/structural crafting
ProcessingFurnace/processing blocks
MemoriesMemory/collection UI

ContainerWindow Example

1import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow;
2import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
3
4// Create container window from an ItemContainer
5ItemContainer container = // ... get or create container
6ContainerWindow window = new ContainerWindow(container);
7
8// Register close event
9window.registerCloseEvent(event -> {
10 // Handle window close
11 getLogger().info("Container closed");
12});
13
14// Open the window
15windowManager.openWindow(window);

Notifications

Toast-style notifications that appear briefly on screen.

1import com.hypixel.hytale.protocol.packets.interface_.Notification;
2import com.hypixel.hytale.protocol.packets.interface_.NotificationStyle;
3
4// Create notification packet
5Notification notification = new Notification(
6 formattedMessage, // Main message
7 secondaryMessage, // Secondary text (nullable)
8 "icons/achievement", // Icon path (nullable)
9 itemWithMetadata, // Item to display (nullable)
10 NotificationStyle.Success
11);
12
13// Send to player
14player.getPlayerConnection().write(notification);

NotificationStyle

styleuse case
DefaultGeneral information
SuccessPositive feedback (green)
WarningCaution notices (yellow)
DangerErrors/critical info (red)

Kill Feed

1import com.hypixel.hytale.protocol.packets.interface_.KillFeedMessage;
2
3// Create kill feed entry
4KillFeedMessage killFeed = new KillFeedMessage(
5 killerFormattedMessage, // Killer name
6 victimFormattedMessage, // Victim name
7 "icons/sword" // Weapon/cause icon
8);
9
10// Send to player
11player.getPlayerConnection().write(killFeed);

UI Value Types

Special types for complex UI values:

Value<T>

1import com.hypixel.hytale.server.core.ui.Value;
2
3// Direct value
4Value<String> directValue = Value.of("Hello");
5
6// Reference to another UI element's value
7Value refValue = Value.ref("OtherPage.ui", "#element.property");

Area

1import com.hypixel.hytale.server.core.ui.Area;
2
3Area area = new Area()
4 .setX(10)
5 .setY(20)
6 .setWidth(100)
7 .setHeight(50);
8
9commands.set("#element.area", area);

Anchor

1import com.hypixel.hytale.server.core.ui.Anchor;
2
3Anchor anchor = new Anchor();
4anchor.setLeft(Value.of(10));
5anchor.setTop(Value.of(20));
6anchor.setWidth(Value.of(200));
7anchor.setHeight(Value.of(100));
8
9// Or use shortcuts
10anchor.setFull(Value.of(0)); // Fill parent
11anchor.setHorizontal(Value.of(10)); // Horizontal margins
12anchor.setVertical(Value.of(10)); // Vertical margins

PatchStyle

1import com.hypixel.hytale.server.core.ui.PatchStyle;
2
3PatchStyle style = new PatchStyle()
4 .setTexturePath(Value.of("textures/button.png"))
5 .setBorder(Value.of(4))
6 .setColor(Value.of("#FF0000"));
7
8commands.set("#button.background", style);

ItemGridSlot

1import com.hypixel.hytale.server.core.ui.ItemGridSlot;
2
3ItemGridSlot slot = new ItemGridSlot(itemStack)
4 .setName("Magic Sword")
5 .setDescription("A powerful weapon")
6 .setActivatable(true)
7 .setItemIncompatible(false);
8
9commands.set("#inventory-slot-0", slot);

LocalizableString

1import com.hypixel.hytale.server.core.ui.LocalizableString;
2
3// From raw string
4LocalizableString text = LocalizableString.fromString("Hello World");
5
6// From localization key (for translations)
7LocalizableString localized = LocalizableString.fromMessageId("ui.welcome.title");
8
9// With parameters
10Map<String, String> params = Map.of("player", playerName);
11LocalizableString withParams = LocalizableString.fromMessageId("ui.welcome.greeting", params);

DropdownEntryInfo

1import com.hypixel.hytale.server.core.ui.DropdownEntryInfo;
2
3// For dropdown/select elements
4DropdownEntryInfo entry = new DropdownEntryInfo(
5 LocalizableString.fromString("Option 1"),
6 "value_1" // Internal value
7);

Built-in Pages

Hytale provides some pre-built page types:

Page Enum (Built-in)

pagedescription
NoneNo page (close current)
InventoryPlayer inventory
BenchCrafting bench
MapWorld map
ToolsSettingsTool configuration
MachinimaEditorMachinima/cinematic editor
ContentCreationContent creation tools
CustomCustom page marker
1// Open built-in page
2pageManager.setPage(ref, store, Page.Inventory);
3
4// Close any page
5pageManager.setPage(ref, store, Page.None);

RespawnPage

1import com.hypixel.hytale.server.core.entity.entities.player.pages.RespawnPage;
2
3// Built-in respawn screen
4RespawnPage respawnPage = new RespawnPage(
5 playerRef,
6 Message.raw("You Died"), // Death message
7 true, // Allow respawn
8 deathItemLoss // Item loss config
9);
10
11pageManager.openCustomPage(ref, store, respawnPage);

ChoiceBasePage

For dialog/choice-based interactions (NPC dialogs, etc.):

1import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.*;
2
3// ChoiceElement represents a selectable option
4// Extend ChoiceBasePage for custom dialog pages
5// Use ChoiceInteraction for what happens when selected
6// Use ChoiceRequirement for conditions to show/enable options

Common Patterns

Complete Shop Example

FullShopPage.javajava
1public class FullShopPage extends InteractiveCustomUIPage {
2
3 private final List items;
4 private int coins;
5
6 public FullShopPage(PlayerRef playerRef, List items, int coins) {
7 super(playerRef, CustomPageLifetime.CanDismiss, EventData.CODEC);
8 this.items = items;
9 this.coins = coins;
10 }
11
12 @Override
13 public void build(Ref ref, UICommandBuilder cmd,
14 UIEventBuilder evt, Store store) {
15
16 cmd.append("Pages/Shop.ui");
17 cmd.set("#coins", coins);
18
19 for (int i = 0; i < items.size(); i++) {
20 ShopItem item = items.get(i);
21
22 // Add item entry to list
23 cmd.append("#item-list", "Components/ShopItem.ui");
24 cmd.set("#item-" + i + "-name", item.name);
25 cmd.set("#item-" + i + "-price", item.price);
26 cmd.set("#item-" + i + "-icon", item.icon);
27
28 // Bind buy button
29 evt.addEventBinding(
30 CustomUIEventBindingType.Activating,
31 "#item-" + i + "-buy",
32 EventData.of("Action", "buy")
33 .append("index", String.valueOf(i))
34 );
35 }
36
37 // Close button
38 evt.addEventBinding(
39 CustomUIEventBindingType.Activating,
40 "#close",
41 EventData.of("Action", "close")
42 );
43 }
44
45 @Override
46 public void handleDataEvent(Ref ref, Store store,
47 EventData data) {
48 switch (data.action) {
49 case "buy":
50 int index = Integer.parseInt(data.index);
51 ShopItem item = items.get(index);
52
53 if (coins >= item.price) {
54 coins -= item.price;
55 // Give item to player...
56
57 // Update UI
58 UICommandBuilder update = new UICommandBuilder();
59 update.set("#coins", coins);
60 // sendUpdate(update);
61 }
62 break;
63
64 case "close":
65 // close();
66 break;
67 }
68 }
69
70 public static class EventData {
71 public static final BuilderCodec CODEC = /* ... */;
72 public String action;
73 public String index;
74 }
75}

Dynamic HUD Updates

1public class QuestTrackerHud extends CustomUIHud {
2
3 private List activeQuests;
4
5 public void addQuest(Quest quest) {
6 activeQuests.add(quest);
7 refreshQuestList();
8 }
9
10 public void updateQuestProgress(String questId, int progress) {
11 UICommandBuilder update = new UICommandBuilder();
12 update.set("#quest-" + questId + "-progress", progress);
13 update(false, update);
14 }
15
16 public void completeQuest(String questId) {
17 UICommandBuilder update = new UICommandBuilder();
18 update.remove("#quest-" + questId);
19 update(false, update);
20 }
21
22 private void refreshQuestList() {
23 UICommandBuilder update = new UICommandBuilder();
24 update.clear("#quest-list");
25
26 for (Quest quest : activeQuests) {
27 update.append("#quest-list", "Components/QuestEntry.ui");
28 update.set("#quest-" + quest.id + "-name", quest.name);
29 update.set("#quest-" + quest.id + "-progress", quest.progress);
30 }
31
32 update(false, update);
33 }
34}

Known Limitations

work in progress
The UI system is still being reverse-engineered. Some features may not work as expected.

What Works

  • Opening custom pages with openCustomPage()
  • Setting initial values in build() with cmd.set("#Element.Text", value)
  • Button click events with addEventBinding() and handleDataEvent()
  • Closing pages with close()
  • Using Hytale's native templates ($C.@PageOverlay, $C.@Container, $C.@Title)

What Does NOT Work (Yet)

  • sendUpdate() - Dynamic UI updates after initial build don't reflect visually
  • rebuild() - Rebuilding the page doesn't update the display
  • Custom textures/images from plugin JAR - Can't reference external assets
  • Complex styling - Limited to basic colors, no custom PNG backgrounds from plugins
workaround for updates
Since sendUpdate() and rebuild() don't work reliably, consider closing and reopening the page to show updated values, or design your UI to not require dynamic updates.

Troubleshooting

Could not find document

error message
Could not find document Pages/MyPage.ui for Custom UI Append command. Selector:

Common causes:

causesolution
Missing IncludesAssetPackAdd "IncludesAssetPack": true to manifest.json
Wrong path in append()Use "Pages/Name.ui" not "Common/UI/Custom/Pages/Name.ui"
File not in JAREnsure .ui file is in Common/UI/Custom/Pages/ in JAR
Typo in filenamePaths are case-sensitive! Check exact spelling
JAR not rebuiltRecompile and rebuild JAR after changes

Selected element was not found

error message
Selected element in CustomUI command was not found. Selector: #Element.Text

This happens when using sendUpdate() with selectors that worked in build(). The element context is different after the initial build. This is a known limitation.

CustomUI command data must be an object

error message
CustomUI command data must be an object if no property was selected. Selector: #Element

You must specify the property to set. Use #Element.Text not just #Element.

Client Disconnects When Opening UI

If opening UI crashes/disconnects the client:

  • Syntax error in .ui file - check brackets and commas
  • Invalid texture path - don't reference external PNG files
  • Calling openCustomPage outside world thread
  • Null value passed to cmd.set() - always check for null
threading
All UI operations must run on the world thread. Always wrap in world.execute().
event data keys
EventData keys must match your codec exactly. Use EventData.of("Action", "value")where "Action" matches the key in your BuilderCodec.

Package Reference

packagecontents
server.core.uiValue, Area, Anchor, PatchStyle, ItemGridSlot, LocalizableString
server.core.ui.builderUICommandBuilder, UIEventBuilder, EventData
server.core.entity.entities.player.pagesPageManager, CustomUIPage, BasicCustomUIPage, InteractiveCustomUIPage
server.core.entity.entities.player.hudHudManager, CustomUIHud
server.core.entity.entities.player.windowsWindowManager, Window, ContainerWindow
protocol.packets.interface_CustomPage, CustomHud, Notification, HudComponent, etc.