İçeriğe geç
NUI basics: HTML interfaces in FiveM

NUI basics: HTML interfaces in FiveM

Build FiveM UIs with NUI: ui_page and files, SendNUIMessage, RegisterNUICallback with fetch, SetNuiFocus, dev tools, React or Vue with a build step, and common pitfalls.

Bu dokümanlar şimdilik İngilizce.

Last updated

NUI is FiveM’s built in Chromium (CEF) browser. Every resource can have a web page that sits on top of the game: HUDs, menus, phones, inventories, loading screens. You write it with HTML, CSS and JavaScript, or any framework that builds to static files (React, Vue, Svelte, Solid…).

Plays from youtube-nocookie.com after you click.

How it fits together

Text
 Lua client script  ── SendNUIMessage({...}) ──►  your page (window 'message' event)
 Lua client script  ◄── RegisterNUICallback ────  fetch('https://<resource>/<name>')
  • Lua sends data to the page with SendNUIMessage.
  • The page sends data back with an HTTP style fetch to https://<resource-name>/<callback-name>, which triggers a RegisterNUICallback handler in Lua.
  • SetNuiFocus decides whether the page gets the mouse and keyboard.
  • The page never talks to the server directly. If the server needs to know, Lua forwards it with an event.

A minimal example

Text
nui_demo/
├─ fxmanifest.lua
├─ client.lua
└─ html/
   ├─ index.html
   ├─ style.css
   └─ app.js
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

client_script 'client.lua'

ui_page 'html/index.html'

files {
    'html/index.html',
    'html/style.css',
    'html/app.js',
}

Every file the page loads must be listed in files, or the client never downloads it and you get a blank or broken UI.

html/index.html
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div id="panel" class="hidden">
    <h1 id="title">Hello</h1>
    <button id="close">Close</button>
  </div>
  <script src="app.js"></script>
</body>
</html>
html/style.css
html, body { margin: 0; background: transparent; font-family: sans-serif; }
.hidden { display: none; }
#panel {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  padding: 24px; background: rgba(15, 15, 15, 0.9); color: #fff; border-radius: 8px;
}

Keep the body background transparent, or the page covers the game.

html/app.js
const panel = document.getElementById('panel');
const title = document.getElementById('title');

window.addEventListener('message', (event) => {
  const data = event.data;
  if (data.action === 'open') {
    title.textContent = data.title;
    panel.classList.remove('hidden');
  } else if (data.action === 'close') {
    panel.classList.add('hidden');
  }
});

function post(name, body = {}) {
  return fetch(`https://${GetParentResourceName()}/${name}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json; charset=UTF-8' },
    body: JSON.stringify(body),
  }).then((r) => r.json());
}

document.getElementById('close').addEventListener('click', () => post('close'));

document.addEventListener('keyup', (e) => {
  if (e.key === 'Escape') post('close');
});

GetParentResourceName() is available inside NUI pages and returns your resource name, so the callback URL stays right if someone renames the folder.

client.lua
local open = false

local function setOpen(state)
    open = state
    SetNuiFocus(state, state)          -- keyboard focus, mouse cursor
    SendNUIMessage({ action = state and 'open' or 'close', title = 'Hello from Lua' })
end

RegisterCommand('demo', function()
    setOpen(not open)
end, false)

RegisterNUICallback('close', function(data, cb)
    setOpen(false)
    cb({ ok = true })                  -- always answer, or fetch() hangs
end)

ensure nui_demo, type /demo, and the panel appears with a mouse cursor. Esc or the button closes it.

Focus

SetNuiFocus(hasFocus, hasCursor):

  • SetNuiFocus(true, true): the page gets keyboard and mouse. The player can’t move or use game controls. Use for menus.
  • SetNuiFocus(false, false): the page is display only (HUDs). The game keeps control.
  • SetNuiFocusKeepInput(true) lets the game still receive keyboard input while the page has focus, for UIs where the player keeps walking. Disable the controls you don’t want yourself.

Warning

Always give the player a way out (Esc, a close button) and release focus on close. A UI that keeps focus traps the player, who has to quit the game. Also release focus on onResourceStop.

If your UI opens with Tab (inventories do), check IsNuiFocused() first so it doesn’t steal focus from another open UI like the txAdmin menu.

Debugging NUI

Type nui_devtools in the F8 console to open Chrome DevTools for NUI: console logs, element inspector, network tab. Errors in your JavaScript show there, not in F8.

Other tips:

  • Develop the page in a normal browser first, faking the message events with window.postMessage({ action: 'open', title: 'test' }).
  • ui_page can point to a dev server URL (ui_page 'http://localhost:5173') while you work, so hot reload works. Switch back to the built file before you ship.

React, Vue and friends

Frameworks work fine. You build to static files and point ui_page at the built index.html:

fxmanifest.lua
ui_page 'web/build/index.html'

files {
    'web/build/index.html',
    'web/build/**/*',
}

Loading screens

A loading screen is a special NUI page shown while the player joins:

fxmanifest.lua
loadscreen 'html/index.html'
files { 'html/**/*' }
-- optional: keep it until you close it yourself
loadscreen_manual_shutdown 'yes'

With manual shutdown, call ShutdownLoadingScreenNui() from a client script when your spawn is ready, or players stay on the loading screen forever.

Performance

  • NUI pages that animate constantly (CSS animations, requestAnimationFrame, big videos) cost GPU and CPU on every client. Pause them when hidden.
  • Don’t SendNUIMessage every frame. Send when data changes, or a few times per second for HUD values.
  • Keep images reasonable in size. Every file in files is downloaded by every player.

Common mistakes

Symptom Cause
Blank UI ui_page or its assets not listed in files, wrong path, or absolute asset paths in a framework build.
fetch never resolves The Lua RegisterNUICallback handler didn’t call cb().
UI shows but clicks do nothing SetNuiFocus(true, true) not called.
Player stuck with a cursor Focus not released on close or on resource stop.
White or black background over the game body or html background isn’t transparent.
Works in browser, not in game Using APIs NUI doesn’t allow, or the callback URL doesn’t match the resource name. Use GetParentResourceName().

Next: Exports and dependencies.