Debug FiveM NUI: nui_devtools, console errors and failed callbacks
Debug a FiveM NUI: open Chromium devtools with nui_devtools, read Uncaught TypeError errors, fix a failed fetch to https://resource/cb and test the UI in a browser.
Your NUI opens as a blank panel, a button does nothing, or the cursor appears and no screen follows. There is usually no Lua error, because the fault is in the web page. You need the browser console, and FiveM has one. This guide covers how to open it, how to read its errors and how to test your UI without the game.
Open the devtools: nui_devtools
NUI is a Chromium page inside the game. Press F8 to open the FiveM console and run:
nui_devtoolsA devtools window opens for the NUI layer, like the one you know from a browser. Use these tabs:
- Console: JavaScript errors and your own
console.loglines. - Network: every request the page makes, including the callbacks to Lua.
- Elements: the live HTML and CSS. Use it to see whether your element exists, how large it is and whether it is hidden.
- Sources: set breakpoints in your script.
Warning: whether
nui_devtoolsis available depends on your settings and on the client build you run. If the command does nothing, check that your game client is up to date, and test the UI in a normal browser as shown below.
If your UI is visible only after a SendNUIMessage, open the devtools first and trigger the action afterwards, so you see the messages that arrive.
Read the console errors
Errors in the Console tab show the file and the line of your script. The ones you will see most:
Uncaught TypeError: Cannot read properties of undefined (reading 'items')Your code read .items on something that is undefined. In NUI this almost always comes from the message:
window.addEventListener('message', (event) => {
const { action, data } = event.data
if (action === 'open') {
render(data.items) // data is undefined
}
})Check what Lua really sent. Print the whole message first:
window.addEventListener('message', (event) => {
console.log('NUI message', JSON.stringify(event.data))
})Then compare it to the Lua call:
SendNUIMessage({ action = 'open', data = { items = items } })The keys must match exactly, including case. A field set to nil in Lua is missing from the message, so it is undefined in JavaScript.
Other common errors:
| Console message | Usual cause |
|---|---|
Uncaught ReferenceError: x is not defined |
A script file did not load, or a typo in a name |
Uncaught SyntaxError |
A broken script file, or a bundler output with the wrong path |
Failed to load resource: 404 |
A file is not listed in files in the fxmanifest, or the path is wrong |
Uncaught (in promise) TypeError: Failed to fetch |
A callback request failed, see below |
If the Network tab shows a 404 for your JS, CSS or images, add every file the page loads to files in the manifest, and point ui_page to the right HTML:
ui_page 'web/index.html'
files {
'web/index.html',
'web/**/*',
}For a Vite or React build, use the output folder, see FiveM NUI with React and Vite.
Failed fetch to https://resource/cb
NUI sends messages to Lua with a request to https://<resource name>/<callback name>:
fetch(`https://${GetParentResourceName()}/close`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
})In the Network tab a failed request shows in red:
POST https://my_script/close net::ERR_FAILEDCheck these in order:
- The callback is not registered. The name after the slash must match a
RegisterNUICallbackon the client, exactly:
RegisterNUICallback('close', function(data, cb)
SetNuiFocus(false, false)
cb('ok')
end)- The resource name is wrong. The host in the URL is the resource folder name. If you renamed the folder, a hard-coded
https://old_name/closefails. UseGetParentResourceName()in the page, as above, so the name follows the folder. - The callback never answers. Always call
cb(...), even withcb('ok'). A handler that returns without calling it leaves the request pending, and the page can wait forever. - The Lua handler errors before
cb. Check the F8 console for a script error at the same moment. An error beforecbmeans the answer is never sent. - The resource is not running. Callbacks live as long as the client script that registered them.
Focus and the cursor
If the UI shows but cannot be clicked, or the mouse stays stuck after closing, the problem is SetNuiFocus, not the page. See NUI focus stuck cursor.
Test the UI in a normal browser
Debugging in game is slow. Most of a NUI can be tested in a browser, with your editor's live reload.
Open index.html through a local server, or run npm run dev for a Vite project. Then stub the two things the game normally provides.
First, a fake GetParentResourceName, and callbacks that do not fail:
if (!window.GetParentResourceName) {
window.GetParentResourceName = () => 'my_script'
window.fetch = async (url, options) => {
console.log('NUI callback', url, options && options.body)
return { ok: true, json: async () => ({}) }
}
}Second, send the messages that Lua would send. From the browser console, or a dev-only button:
window.postMessage({ action: 'open', data: { items: [{ name: 'water', count: 3 }] } }, '*')This calls your message listener exactly as SendNUIMessage does. You can design, fix layout and check data handling in the browser, and keep the game for the final check of focus, real data and real callbacks.
Tip: remove the stubs from the production build, or wrap them so they only run when the page is opened outside the game, as above with the
ifonGetParentResourceName.
Checklist
| Symptom | Fix |
|---|---|
| Blank NUI | Open nui_devtools, read the Console and Network tabs |
nui_devtools does nothing |
Update the client and check your settings; test in a normal browser |
Uncaught TypeError ... of undefined |
Log event.data and match the keys sent by SendNUIMessage |
| 404 for a file | List it in files and check ui_page |
POST https://resource/name fails |
Register the callback with RegisterNUICallback and use the exact name |
| Wrong resource name in the URL | Use GetParentResourceName() |
| Button works, nothing happens in Lua | Make sure the callback calls cb('ok') |
| Cursor stuck after closing | Call SetNuiFocus(false, false) |
Quick answers
How do I open the devtools for a FiveM NUI?
Open the F8 console in game and run nui_devtools. A Chromium devtools window opens for the NUI layer. Availability can depend on your settings and client build.
Why does my NUI callback fail?
The request to https://resource_name/callback did not reach Lua. The callback is not registered with RegisterNUICallback, the resource name in the URL is wrong, or the callback never answers with cb.
Can I test my NUI without starting the game?
Yes. Open the page in a normal browser or run the Vite dev server, send fake messages with window.postMessage, and stub the callbacks. Only the real game gives you focus, real data and real callbacks.
Scripts that skip this problem
Mic PhoneA foldable phone that unfolds into a tablet and carries onto a player's real phone.View script →
Arcade MachinesSeven playable arcade games in real cabinets, with leaderboards and bets.View script →
CCTV Security CamerasPlaceable cameras, a live multi-view tablet and printed evidence photos.View script →