Skip to Content
ScriptingNUI Callback APIs

Register and complete NUI callbacks

The current source manual  uses RegisterNuiCallback, backed by REGISTER_NUI_CALLBACK, in Lua, JavaScript and C#. Data in both directions must be JSON-encodable. Every accepted browser request must complete its callback exactly once, including validation failures.

The older Lua helper is spelled RegisterNUICallback. Existing resources may also register an NUI callback type and handle a __cfx_nui: event. Those are compatibility paths, not a reason to register the same endpoint twice. Select one mechanism supported by the target artifact. A source review does not prove a newer native exists in an older installed artifact.

Lua client

Add this to a resource’s client.lua. The table is private example data, not a framework inventory or a permission source:

local labels = { water = 'Water', bread = 'Bread' } RegisterNuiCallback('lookupLabel', function(data, cb) if type(data) ~= 'table' or type(data.id) ~= 'string' or #data.id > 32 then cb({ ok = false, error = 'invalid_id' }) return end local label = labels[data.id] if not label then cb({ ok = false, error = 'not_found' }) return end cb({ ok = true, label = label }) end)

JavaScript client

Use this instead of the Lua registration when the resource uses client.js:

const labels = new Map([['water', 'Water'], ['bread', 'Bread']]); RegisterNuiCallback('lookupLabel', (data, cb) => { if (!data || typeof data.id !== 'string' || data.id.length > 32) { cb({ ok: false, error: 'invalid_id' }); return; } const label = labels.get(data.id); cb(label === undefined ? { ok: false, error: 'not_found' } : { ok: true, label }); });

C# client

Within the constructor of a client BaseScript, the corresponding registration is below. Import System, System.Collections.Generic, CitizenFX.Core, and the static CitizenFX.Core.Native.API wrappers in a project whose target assembly provides RegisterNuiCallback:

RegisterNuiCallback("lookupLabel", new Action<IDictionary<string, object>, CallbackDelegate>((data, cb) => { if (data == null || !data.TryGetValue("id", out var raw) || !(raw is string id) || id.Length > 32) { cb(new { ok = false, error = "invalid_id" }); return; } var label = id == "water" ? "Water" : id == "bread" ? "Bread" : null; if (label == null) { cb(new { ok = false, error = "not_found" }); return; } cb(new { ok = true, label }); }));

This is a constructor fragment, not a complete new project. The compiled C# lab documents project structure, legacy bridge compatibility and task lifetime separately. Do not interpret its successful legacy build as a compilation test of every newer SDK-native wrapper.

Browser request and visible failure

Load this function in the resource’s NUI page. The manifest must declare the ui_page and include its HTML/JavaScript files. GetParentResourceName() is supplied by FiveM’s NUI environment, not by an ordinary website tab.

async function lookupLabel(id) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const response = await fetch(`https://${GetParentResourceName()}/lookupLabel`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8' }, body: JSON.stringify({ id }), signal: controller.signal, }); if (!response.ok) throw new Error('NUI transport failed.'); const result = await response.json(); if (!result || result.ok !== true || typeof result.label !== 'string') { throw new Error('The item label is unavailable.'); } return result.label; } finally { clearTimeout(timeout); } }

The calling UI should display the resolved label using textContent and show a retryable error when the promise rejects. Do not use returned text as innerHTML. Browser abortion bounds the UI wait; it does not automatically cancel work already started in the game or server.

Asynchronous operations and security

A reply callback completes transport; it does not authorize a purchase, grant an item or prove a database commit. Forward privileged actions to the server, validate them there and use request IDs plus session/resource generation checks. Give each operation a bounded deadline and a completion guard so that timeout, success, failure and resource shutdown cannot respond twice.

Do not leave the callback unresolved after an exception or an invalid input. Use a sanitized error object. A resource restart or closed UI may make a late result irrelevant; discard it instead of reopening the interface. Advanced NUI covers focus cleanup, and secure events covers the server boundary.

Test matrix

Submit water, an unknown string, a missing ID, null, a non-string ID and an overlong ID. Each registered handler should complete exactly once. Then stop/restart the resource while a browser request is pending and verify that the UI rejects or times out without trapping input. The JavaScript handler can be tested directly with a mocked registration function; HTTP dispatch, Lua/C# native availability and focus behavior still require the intended client/server pair.