WebUI
A WebUI is essentially an in-game browser that can display a custom website you have built using HTML, CSS, and JavaScript. It is a powerful way to create GUI elements for your server quickly and flexibly.
The JavaScript on a web page and a JavaScript resource are two different things. The page runs in the browser with the browser's API and talks to your resource through the events below; it has no access to the script functions or the game.
Preparing Your WebUI
The easiest workflow is:
- Fully build and test your HTML page in your regular system browser.
- Leave only the interfaces (functions, event hooks, etc.) open that will later communicate with your scripts or receive data.
Adding the WebUI to Your Resource
For players to see your WebUI, they must download all required files when connecting to the server.
- Place all your WebUI files in a dedicated folder inside your resource (e.g.,
UI). - Define these files in your meta file so the client knows to download them:
<meta type="lua">
<file src="UI/index.html" />
<file src="UI/style.css" />
<file src="UI/script.js" />
</meta>
Every file the page loads, including images, fonts and JSON files you fetch(), has to be listed.
A file that is not listed is not on the player's PC, and the page gets a 404 for it.
Creating the WebUI
Once the player has all the necessary files, you can create the WebUI at any time:
- Lua
- JavaScript
- Squirrel (deprecated)
local l_webui = nil
Events.Subscribe("scriptInit", function()
l_webui = WebUI.Create("resource://myresource/UI/index.html", 1920, 1080, true)
end)
let l_webui = null;
Events.Subscribe("scriptInit", () => {
l_webui = WebUI.Create("resource://myresource/UI/index.html", 1920, 1080, true);
});
local l_webui = null;
Events.Subscribe("scriptInit", function() {
l_webui = WebUI.Create("resource://myresource/UI/index.html", 1920, 1080, true);
});
When a WebUI is created, it is displayed immediately.
The WebUI API is designed for on-demand creation: you create it when you want to show it, and destroy it when it is no longer needed. To hide a UI only for a moment and keep the page as it is, use SetVisible instead.
Even if your WebUI consists of multiple files, you always create it using the HTML file. CSS and JS files should be loaded in the HTML using standard HTML syntax.
In the example above, the WebUI is created during the scriptInit event, right when the player joins the server. The URL points to a file of the resource, but external http:// and https:// URLs are also supported.
Resource URLs
Files of a resource are loaded with this URL format:
resource://[RESOURCE_NAME]/[PATH_TO_FILE]
file://[RESOURCE_NAME]/[PATH_TO_FILE] is accepted as well and is loaded the same way, so both of
these create the same page:
resource://myresource/UI/index.html
file://myresource/UI/index.html
What this means for your page:
- Every resource is its own website.
localStorageandIndexedDBbelong to the resource and are kept on the player's PC between sessions. Two resources never see each other's data. fetch()works for the files of your resource, with paths relative to the page:fetch("data/items.json"). Audio and video files can be played and seeked.- Relative paths stay inside the resource.
../cannot go above the resource folder. To use a file of another resource, write its full URL, for example<img src="resource://otherresource/images/logo.png">. Resources can load andfetch()each other's files. - Query and fragment are passed to the page, so one HTML file can show different things:
resource://myresource/UI/index.html?page=shop#items. - Pages cannot open files from the player's disk (
file:///C:/...).
Resources in category folders use their full name in the URL, for
example resource://gamemodes/freeroam/UI/index.html. The first folder of that name is what
identifies the website, so all resources inside the gamemodes folder share their localStorage.
Use a key prefix per resource if that matters to you.
The first part of the URL is used like a domain name, so it may only contain letters, digits, -
and _, may be at most 63 characters long and may not look like a number (such as 123). For a resource (or category folder) with any
other name, resource:// raises a script error. file:// still works for it, but the page is then
opened as a plain local file: it has no website of its own and cannot use fetch() for its files.
Communicating with Scripts
Communication between your script and the WebUI works via events, just like between regular scripts.
From your script to the web page:
- In the web page's JavaScript file, subscribe to an event:
Events.Subscribe("myJSEvent", function(arg1, arg2) {
console.log("myJSEvent called!", arg1, arg2);
});
- From your script, trigger the event:
- Lua
- JavaScript
- Squirrel (deprecated)
WebUI.CallEvent(l_webui, "myJSEvent", { "test", 11 })
WebUI.CallEvent(l_webui, "myJSEvent", [ "test", 11 ]);
WebUI.CallEvent(l_webui, "myJSEvent", [ "test", 11 ]);
You can call events right after creating the WebUI. Until the page has loaded, they are kept and delivered in order as soon as it is ready, up to 256 per WebUI.
An error thrown in a handler of the page is reported like any uncaught error of the page: in the
console of the developer tools and in logs/webui.log, with the event
name and the line. The other handlers of the event still run.
From the web page to your script:
Events.Call("myScriptEvent", [arg1, arg2]);
The event arrives in your resource like any other event, whatever language the resource is written in. The arguments are converted like all event arguments, see Event Arguments for the supported types and how numbers are handled.
Events the game triggers itself, such as scriptInit, webUIReady, resourceStart or chatCommand, cannot be sent from a page. Such calls are ignored and only logged as a warning.
Treat everything a page sends like input from the player. With the developer tools
anyone can call Events.Call with any arguments, so check the values before your scripts act on
them, and never let the server trust them blindly.
Which WebUI Sent the Event
If a resource shows the same page more than once (for example one window per vehicle), the handler can ask which WebUI the event came from:
- Lua
- JavaScript
- Squirrel (deprecated)
Events.Subscribe("closeWindow", function()
local id = WebUI.GetEventSource()
WebUI.Destroy(id)
end)
Events.Subscribe("closeWindow", () => {
const id = WebUI.GetEventSource();
WebUI.Destroy(id);
});
Events.Subscribe("closeWindow", function() {
local id = WebUI.GetEventSource();
WebUI.Destroy(id);
});
GetEventSource only answers while the handler runs. Read it at the
start of the handler, not later in a thread or after an await.
Keyboard & Mouse Input
If your WebUI requires keyboard or mouse interaction (e.g., clicking buttons, typing text), you must first set focus to it:
- Lua
- JavaScript
- Squirrel (deprecated)
WebUI.SetFocus(l_webui)
WebUI.SetFocus(l_webui);
WebUI.SetFocus(l_webui);
You can do this right after creation. Focus is not set automatically because some UIs (like a speedometer) do not require input.
Only one WebUI can have the focus. GetFocus tells you which one, and
WebUI.SetFocus(-1) releases it. A hidden WebUI cannot be focused, and hiding or destroying the
focused WebUI releases the focus, so the player never ends up stuck without game controls.
Showing, Hiding and Layering
| You want to | Use |
|---|---|
| Hide a UI for a moment and keep its page | SetVisible |
| Move it or change how large it is drawn | SetRect |
| Change the page's own resolution | SetSize |
| Put one UI in front of another | SetZIndex |
| Save performance on a UI that rarely changes | SetFrameRate |
A hidden WebUI is not drawn and its page stops rendering. Its JavaScript keeps running and it still
receives events, but timers and animations run much slower and requestAnimationFrame pauses
until it is shown again. The page can check document.visibilityState.
SetRect and SetSize do different things. SetRect only decides where and how large the UI
appears on the screen, the page itself does not notice. SetSize changes the size of the browser
window the page runs in: the page gets a resize event and lays itself out again. Until you call
SetRect for the first time, the screen area follows SetSize.
Changing the Page
LoadURL opens another page in an existing WebUI, and
Reload loads the current one again. In both cases
webUIReady is triggered again once the page has loaded, and
IsReady returns false until then. Events sent in between are kept
and delivered to the new page once it has loaded.
The page also starts over in other situations: when it reloads itself (location.reload()), when
it navigates through a link, and when the game restores the WebUI after the browser crashed. Each
time webUIReady comes again, so send the data a page needs from your webUIReady handler instead
of only once after Create.
Lifetime and Limits
- A WebUI belongs to the resource that created it. When the resource stops or restarts, its WebUIs are destroyed.
- IDs are never reused. An ID of a destroyed WebUI stays invalid, so it cannot accidentally point to a newer WebUI.
- At most 10 WebUIs can exist at the same time on a client. The chat counts as one, and all resources share the limit.
- Width and height of a WebUI can be 1 to 8192 pixels. Every WebUI uses memory for its full size, so create it with the resolution you need, not larger.
Debugging with DevTools
The Chrome developer tools work with WebUIs: inspect elements, read the console, debug the page's
JavaScript and watch network requests. They are off by default. To turn them on, add a port to the
settings.xml next to HappinessMP.exe on your PC and start the game again:
<settings>
<webuiDebugPort>9222</webuiDebugPort>
</settings>
Then open chrome://inspect in Chrome or edge://inspect in Edge, click Configure and add
localhost:9222. Every open WebUI appears in the list with its URL, inspect opens the tools for
it. Firefox cannot open them.
The client console command devtools (F1) shows the address and lists the open WebUIs. Additional
clients started with newinstance use the next ports (9223, 9224,
and so on).