Build your first NUI with HTML, CSS, and JavaScript
NUI is FiveM's name for the in-game browser layer, a real Chromium tab the client renders on top of GTA. Every menu, hud, scoreboard, and pause UI you have ever seen in a server is NUI. So far you have used ox_lib and other framework UIs that wrap NUI for you. (ox_lib is a community library from the Overextended team, not a Cfx.re built-in, but it is everywhere.) This bonus shows the layer underneath: how to declare a page in fxmanifest.lua, how to push state from Lua with SendNUIMessage, how to receive clicks with RegisterNUICallback, and how to capture the cursor with SetNuiFocus. Once you understand these four pieces, you can read any NUI resource and build your own without a framework.
Build it
Make the resource folder
Inside your server's resources folder, create this folder:
resources/qu_nui_from_scratch
Create the files
Create this exact file layout:
resources/qu_nui_from_scratch/
fxmanifest.lua
client.lua
web/index.html
web/app.js
web/style.css
Write fxmanifest.lua
Open fxmanifest.lua and paste this:
fx_version 'cerulean'
game 'gta5'
client_script 'client.lua'
ui_page 'web/index.html'
files {
'web/index.html',
'web/app.js',
'web/style.css'
}
Older tutorials add a lua54 'yes' line here. As of June 2025 that setting is deprecated and ignored: Lua 5.4 is the only Lua runtime, so you leave it out.
Write the lesson code
Open client.lua and paste this:
RegisterCommand('openui', function()
SetNuiFocus(true, true)
SendNUIMessage({ action = 'open', title = 'NUI from scratch' })
print('[qu_nui_from_scratch] opened, focus captured')
end, false)
RegisterNUICallback('buttonClicked', function(data, cb)
SetNuiFocus(false, false)
local label = data.label or 'unknown'
print('[qu_nui_from_scratch] callback received, label = ' .. label)
TriggerEvent('chat:addMessage', {
color = { 0, 200, 120 },
args = { 'NUI', 'Round-trip complete. JS sent: ' .. label }
})
cb({ ok = true })
end)
Open web/index.html and paste this:
<!doctype html>
<html>
<head><link rel="stylesheet" href="style.css" /></head>
<body>
<div id="menu" class="hidden">
<h1 id="title">menu</h1>
<button id="action">Send to Lua and close</button>
</div>
<script src="app.js"></script>
</body>
</html>
Open web/app.js and paste this:
const menu = document.getElementById('menu');
const title = document.getElementById('title');
const button = document.getElementById('action');
// Lua to JS: reveal the menu only when Lua pushes the open message.
window.addEventListener('message', (event) => {
if (event.data.action === 'open') {
title.textContent = event.data.title;
menu.classList.remove('hidden');
}
});
// JS to Lua: send a real payload back, then hide on the callback's reply.
button.addEventListener('click', () => {
fetch(`https://${GetParentResourceName()}/buttonClicked`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ label: 'Send to Lua and close' })
}).then(() => {
menu.classList.add('hidden');
});
});
Open web/style.css and paste this:
body { margin: 0; display: grid; place-items: center; min-height: 100vh; font-family: sans-serif; }
#menu { background: #14161a; color: #fff; padding: 28px; border-radius: 10px; text-align: center; }
.hidden { display: none; }
Start and test it
Open server.cfg and add this line:
ensure qu_nui_from_scratch
Save, then run this in your server console (the FXServer window, or the txAdmin Live Console if you run txAdmin):
restart qu_nui_from_scratch
Now join the server. Run this test from the in-game chat box (press T to open it, type the command, press Enter):
/openui, then click "Send to Lua and close"
And in the chat box you see one line: NUI: Round-trip complete. JS sent: Send to Lua and close. That chat line is the proof the data made it from the browser back into Lua, not just an empty request.
Keep reading the full lesson
Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.
- How it works
- A different NUI entry point: the loading screen
- If something went wrong
- What you can do now
- Try it yourself
The remainder of Build your first NUI with HTML, CSS, and JavaScript is available to FiveM School members.