NUI callbacks: sending data from the UI to Lua
In Lua, RegisterNuiCallback('name', function(data, cb) ... cb(result) end). In the page, fetch(https://${GetParentResourceName()}/name, { method: 'POST', body: JSON.stringify(data) }) and read the JSON response. Always call cb, even on errors, or the request never resolves. Treat callback data like any client input: validate on the server before anything that matters.
Messages go from Lua to the page; callbacks go the other way. When a player clicks “Buy”, the page makes a POST request to its own resource, Lua receives the data, does its work and answers — and the page gets that answer back as the fetch response.
Registering the callback
RegisterNuiCallback('buyItem', function(data, cb)
if type(data.item) ~= 'string' then
cb({ ok = false, error = 'bad item' })
return
end
local ok = lib.callback.await('shop:buy', false, data.item, data.amount)
cb({ ok = ok })
end)RegisterNuiCallback exists in Lua, JavaScript and C#; Lua also accepts the older spelling RegisterNUICallback. The data arrives already parsed from JSON.
Calling it from the page
async function buy(item, amount) {
const res = await fetch(`https://${GetParentResourceName()}/buyItem`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify({ item, amount }),
});
const result = await res.json();
if (!result.ok) showError(result.error || 'Purchase failed');
}A reusable helper with browser mocks
In a normal browser (while developing) GetParentResourceName does not exist. A helper that detects the game and returns mock data lets you build the UI in Chrome with hot reload.
const isGame = () => typeof (window as any).GetParentResourceName === 'function';
export async function fetchNui<T = unknown>(name: string, data: unknown = {}, mock?: T): Promise<T> {
if (!isGame()) return mock as T;
const res = await fetch(`https://${(window as any).GetParentResourceName()}/${name}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify(data),
});
return res.json();
}
// usage
const result = await fetchNui<{ ok: boolean }>('buyItem', { item: 'water', amount: 2 }, { ok: true });JavaScript and C# client scripts
RegisterNuiCallback('buyItem', (data, cb) => {
if (typeof data.item !== 'string') return cb({ ok: false });
emitNet('shop:buy', data.item, data.amount);
cb({ ok: true });
});Callbacks are client input
Common problems
| Symptom | Cause |
|---|---|
| Request stays pending forever | cb never called on some code path |
| Request fails immediately | Wrong resource name in the URL, or no callback registered under that name |
GetParentResourceName is not defined | Running in a normal browser — use the mock helper |
Unexpected end of JSON input | The page calls res.json() but cb returned nothing JSON-like — return a table or string |
Common questions
How do I send data from NUI to Lua?
POST to https://${GetParentResourceName()}/<name> from the page and handle it with RegisterNuiCallback in Lua.
Why does my NUI fetch never finish?
The Lua callback did not call cb. Every path, including errors, must call it.
Can I return data to the page?
Yes. Whatever you pass to cb is JSON-encoded and becomes the fetch response.
Are NUI callbacks secure?
No more than any client input. Players can call them from devtools, so validate on the server.
Want a loading screen you never have to debug?
Build it in the browser, export once, and edit it whenever — no HTML, no re-uploads.
Start free