server.json config
Server-side WebGUI settings are stored in config/webgui/server.json.
The file is created automatically on first launch. A config/webgui/server.example.json template is also written for reference. If tokenSecretBase64 is empty, a new secret is generated and saved automatically.
Full example
{
"enableTokens": true,
"tokenTtlSeconds": 900,
"queryParamName": "webgui_token",
"tokenSecretBase64": "<auto-generated or your secret>",
"autoHudOnJoin": false,
"autoHudUrl": "",
"mainMenuUrl": "",
"deathScreenUrl": "",
"serveBundledPages": true,
"bundledPagesDir": "web",
"requireClientMod": false,
"pageEventsPerSecond": 20,
"assetBytesPerSecond": 4194304,
"updateCheckUrl": "",
"trustedCommandOrigins": []
}Fields
Signed tokens
Signed tokens let your web backend verify that a request comes from a genuine WebGUI client.
| Field | Type | Default | Description |
|---|---|---|---|
enableTokens | bool | true | Attach a signed token to every page URL the mod opens. |
tokenTtlSeconds | int | 900 | Token lifetime in seconds. Minimum enforced by the mod: 60. |
queryParamName | string | "webgui_token" | Query parameter name used to carry the token. |
tokenSecretBase64 | string | auto | Base64 HMAC secret shared with your backend. Auto-generated if empty. |
Auto HUD
| Field | Type | Default | Description |
|---|---|---|---|
autoHudOnJoin | bool | false | Automatically open the HUD overlay when a player joins the server. |
autoHudUrl | string | "" | URL to open as the auto HUD. Required when autoHudOnJoin is true. |
Main menu
| Field | Type | Default | Description |
|---|---|---|---|
mainMenuUrl | string | "" | URL sent to clients on join for the main menu screen (opened with F6). Empty = key does nothing. |
Death screen
Replaces the vanilla death screen with a page of your own.
| Field | Type | Default | Description |
|---|---|---|---|
deathScreenUrl | string | "" | Page shown instead of the vanilla death screen. Empty = keep the vanilla screen. |
The feature is opt-in because that screen holds the player's only way to respawn. When it is set:
- The page receives a
webgui:deathevent describing who or what killed the player. - The page respawns them by calling
window.webgui.respawn(). - Esc does not close the page, exactly as the vanilla screen refuses to close — but if the page fails to load, Esc starts respawning again, so a broken page cannot trap anyone.
- Players who die while the browser is still starting get the vanilla screen. Nothing is lost; there is simply no browser yet to draw a page in.
The vanilla HUD keeps rendering behind the page, the same way it does behind the vanilla death screen. Give the page an opaque background if you do not want the hotbar showing through.
Pages served by the server itself
You do not need a web host to use WebGUI. Drop files in config/webgui/web/ and point any URL setting at them with the webgui: scheme:
{ "mainMenuUrl": "webgui:/index.html", "deathScreenUrl": "webgui:/death.html" }The directory is created on first start with a working index.html in it, so there is something to edit before there is anything to configure.
/webgui reload picks up changes without a restart: everyone online is told about the new files, and a bundled page someone already has open is refreshed in place, so you can edit a file and watch the result. Pages loaded from a web host are left alone — reload them the way you would in a browser.
| Field | Type | Default | Description |
|---|---|---|---|
serveBundledPages | bool | true | Serve config/webgui/web/ to players. Harmless when the folder is empty. |
bundledPagesDir | string | "web" | Directory, relative to config/webgui. Cannot point outside it. |
assetBytesPerSecond | int | 4194304 | Page bytes one player may pull per second. 0 disables the limit. |
Files travel down the connection the player is already on — no port to open, nothing exposed, and it works behind NAT. Each one is addressed by its SHA-256, so a client that already has a file never asks for it again, and a changed file is simply a different name.
Read this before you build a page for it
Pages the server hosts covers the whole feature in detail: the origin your page runs on, why absolute asset paths 404 and how to fix that in each bundler, CORS and WebSockets against your backend, every limit, and a symptom-by-symptom troubleshooting table. Most first-time problems are one entry in it.
External hosting still works exactly as before
http:// and https:// URLs are untouched. Mix freely — a bundled page can load scripts from a CDN, and an externally hosted page can pull images out of the bundled set.
A bundled page has a different origin
It is served from http://127.0.0.1:25580, not from your domain, so your API should name that origin in CORS. Note that the in-game browser has web security disabled by default (a Rinku setting), so a cross-origin fetch works even without it — but a player can turn that off, so configure CORS anyway. Pages can read window.webgui.assetsBase instead of hard-coding the port.
Client version checks
| Field | Type | Default | Description |
|---|---|---|---|
requireClientMod | bool | false | Refuse players whose client has no compatible WebGUI, instead of letting them in with a warning. |
On join the server tells the client which WebGUI it runs. What happens next depends on the client:
| Client | requireClientMod: false (default) | requireClientMod: true |
|---|---|---|
| Same protocol | Nothing — everything works. | Nothing. |
| Older WebGUI | Joins; a chat message names both versions. | Refused, with both versions in the disconnect message. |
| No WebGUI | Joins; a chat note if the server actually uses pages. | Refused, told the mod is required. |
Upgrading from 1.7.0 or earlier on NeoForge
Those versions registered their channels as required, so a NeoForge server turned away any client without the exact same WebGUI — reporting it as "Incompatible client! Please use NeoForge <version>", which points at the wrong mod entirely. From 1.7.1 the channels are optional and the player is told what is really wrong. Set requireClientMod: true to keep refusing them, now with a message that says why.
Rate limiting
| Field | Type | Default | Description |
|---|---|---|---|
pageEventsPerSecond | int | 20 | Messages one player's pages may send with postToGame per second. 0 disables the limit. |
Page events are dispatched on the server thread, and a page is web content — a runaway loop in your own JavaScript, or a hostile page a player was talked into opening, can otherwise send them as fast as the connection allows. Anything over the limit is dropped and logged once per player per burst. Raise it if a legitimate UI of yours is chatty.
Update check
| Field | Type | Default | Description |
|---|---|---|---|
updateCheckUrl | string | "" | URL that returns version info. Supported formats: {"version":"1.2.3"} or {"tag_name":"v1.2.3","html_url":"..."}. Empty = disabled. |
Trusted command origins
Pages can run Minecraft commands as the player (see runCommand). Because the webview can load arbitrary web content, this is gated by origin: a command is accepted only from the main frame of an origin listed here.
| Field | Type | Default | Description |
|---|---|---|---|
trustedCommandOrigins | string[] | [] | Origins (scheme://host[:port]) whose pages may run commands. Empty = no page may run commands. |
{ "trustedCommandOrigins": ["https://ui.myserver.example", "http://localhost:3000"] }- Commands run with the player's own permissions — exactly as if typed in chat, so there is no privilege escalation.
- Requests from any other origin (after a redirect, or from an
<iframe>) are dropped. - The list is sent to the client on join and cleared on disconnect, so trust never carries across servers.
- Default ports (443/80) are normalized:
https://x≡https://x:443.
Notes
- If you use signed tokens, your backend must verify them with the same secret defined in
tokenSecretBase64. - The config path is
config/webgui/server.json— note webgui, not webui.