NUI callbacks: sending data from the UI to Lua

10 min read6 sections · 4 questions answered
Short answer

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

client.lua
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

app.js
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.

fetchNui.ts
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

client.js
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

SymptomCause
Request stays pending forevercb never called on some code path
Request fails immediatelyWrong resource name in the URL, or no callback registered under that name
GetParentResourceName is not definedRunning in a normal browser — use the mock helper
Unexpected end of JSON inputThe 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

More guides