# GetParentResourceName, callback URLs and what happens to your fetch

> How NUI callback URLs work in FiveM: GetParentResourceName, the https://resource/name route, JSON body rules, callback variants and raw callbacks.

Source: https://fam-tools.com/guides/fivem-getparentresourcename-nui-callbacks · FAM Tools · published 2026-09-19 · updated 2026-09-19 · about 11 min

**Short answer.** `GetParentResourceName()` is a function FiveM injects into NUI pages; it returns the name of the resource that owns the frame. A POST to `https://<that name>/<callback>` never leaves the game: FiveM intercepts it, parses the body as JSON and hands it to the handler registered under that callback name. If the body is not valid JSON, the handler is never called and the fetch hangs. `RegisterNUICallback` is the older event-based Lua helper, `RegisterNuiCallback` the direct one; both behave the same from the page. `RegisterRawNuiCallback` gives you the raw request (method, path, headers, body) and lets you set the status, headers and body of the response.

## Key points

- `GetParentResourceName()` = the resource that owns the page, read at runtime.
- Never hard-code the resource name; server owners rename folders.
- The body must be valid JSON; invalid JSON silently never reaches Lua.
- An empty body arrives in Lua as `nil`.
- Whatever you pass to `cb` is JSON-encoded and becomes the response.
- Raw callbacks expose method, path, query, headers and body, and let you set the status code.

Every NUI tutorial shows the same line: ``fetch(`https://${GetParentResourceName()}/something`)``. Few explain what that URL is, why it is not a real website, what FiveM does with the body, and why a request can hang forever without a single error. This article follows one request from the page to your Lua function and back, and covers the lesser-known raw callback API for cases the normal one cannot handle.

## What GetParentResourceName is

It is not a browser API and not part of any library. FiveM defines `GetParentResourceName` as a global function inside NUI frames, and it returns the name of the resource whose `ui_page` created the frame. The name is the folder name the server owner used, which is exactly why you read it at runtime: if someone renames `my-phone` to `phone`, a hard-coded URL breaks and `GetParentResourceName()` keeps working.

Because it only exists in game, it is undefined in a normal browser tab during development. Guard it once in a helper rather than scattering `typeof` checks through your code:

*nui.ts*

```ts
declare global {
  interface Window {
    GetParentResourceName?: () => string;
  }
}

export const inGame = typeof window.GetParentResourceName === 'function';

export const resourceName = inGame ? window.GetParentResourceName!() : 'dev-resource';
```

## Following one request

1. The page calls ``fetch(`https://${GetParentResourceName()}/buyItem`, { method: 'POST', body: JSON.stringify({ item: 'water' }) })``.
2. FiveM’s browser layer recognises the host as a resource name and intercepts the request. Nothing goes to the network, so no DNS, no TLS and no CORS apply.
3. The part after the host (`buyItem`) selects the callback registered under that name in that resource.
4. The body is parsed as JSON and converted into a Lua table (or a JS object in a JavaScript client script).
5. Your handler runs and calls `cb(result)`. The result is encoded back to JSON and returned with status 200 and a JSON content type.
6. The page’s `fetch` resolves; `await res.json()` gives you `result`.

The hand-off is asynchronous in both directions. Your handler can wait for a server callback before calling `cb`, and the page simply waits on the promise. The practical helper that wraps this pattern is in [NUI callbacks with fetch](https://fam-tools.com/guides/fivem-nui-callbacks-fetch).

## Body rules that cause silent failures

Looking at how FiveM handles the request explains two of the most confusing bugs. The body is parsed as JSON before your handler is called. If parsing fails, the handler is simply not invoked, so `cb` is never called and the request stays pending with no error on either side.

| Page sends | Handler receives | Result |
| --- | --- | --- |
| `JSON.stringify({ a: 1 })` | `{ a = 1 }` | Works |
| `'{}'` | An empty table | Works; the safest “no data” body |
| No body at all | `nil` | Works, but guard against indexing `nil` |
| `'hello'` (plain text) | Nothing, handler never runs | Fetch hangs forever |
| `{ a: 1 }` (object, not stringified) | Nothing; the body becomes `[object Object]` | Fetch hangs forever |
| `FormData` | Nothing, not JSON | Fetch hangs forever |

> **Always stringify, always send something** — Send `JSON.stringify(data ?? {})` on every call and read `data` defensively in Lua (`data = data or {}`). Those two lines remove a whole category of “my button does nothing” reports.

The response side has its own rule: `cb` must be called exactly once, on every path, and its argument must be encodable. Tables, strings, numbers and booleans are fine. Functions, entity handles you expect to be objects, and tables with mixed or sparse keys produce surprising JSON; see [SendNUIMessage patterns](https://fam-tools.com/guides/fivem-sendnuimessage-patterns) for the encoder’s quirks, which apply equally here.

## RegisterNUICallback, RegisterNuiCallback and RegisterNuiCallbackType

Three names appear in scripts and all of them still work. They differ in how the Lua runtime wires them up, not in what the page sees.

| Function | How it works | When to use |
| --- | --- | --- |
| `RegisterNuiCallback(name, fn)` | Registers a direct function reference with the NUI layer | New code in Lua, JS or C# |
| `RegisterNUICallback(name, fn)` | Lua helper: calls `RegisterNuiCallbackType(name)` and listens to the `__cfx_nui:<name>` event | Existing Lua code; behaves the same |
| `RegisterNuiCallbackType(name)` | Declares the type so requests are routed to the `__cfx_nui:<name>` event | Only when handling that event manually |
| `RegisterRawNuiCallback(name, fn)` | Direct handler that receives the raw HTTP-like request | Non-JSON bodies, custom status codes, sub-paths |

The event-based variant exists for compatibility: some older scripts listen to `__cfx_nui:` events directly. For anything you write today, `RegisterNuiCallback` is the straightforward choice, and in Lua both helpers catch errors thrown inside your handler and print them to the client console as “error during NUI callback”.

## Raw NUI callbacks

`RegisterRawNuiCallback` skips the JSON conversion. Your handler receives a request table and answers with a response table. From FiveM’s source, the request carries `method` (`POST` when there is a body, otherwise `GET`), `path` (whatever follows the callback name, plus the query string), `headers` (a key/value table), `rawHeaders` (an array of `{ key, value }` pairs), `body` (the raw string) and, when known, `resource` (the resource whose page made the call).

*client.lua — a raw callback with sub-paths and status codes*

```lua
RegisterRawNuiCallback('api', function(req, cb)
    if req.method ~= 'POST' then
        cb({ status = 405, body = 'method not allowed' })
        return
    end

    local ok, payload = pcall(json.decode, req.body)
    if not ok or type(payload) ~= 'table' then
        cb({ status = 400, headers = { ['Content-Type'] = 'text/plain' }, body = 'bad json' })
        return
    end

    if req.path == '/notes/save' then
        SetResourceKvp('notes', payload.text or '')
        cb({ status = 200, headers = { ['Content-Type'] = 'application/json' }, body = json.encode({ ok = true }) })
        return
    end

    cb({ status = 404, body = 'unknown route' })
end)
```

*app.js*

```js
const res = await fetch(`https://${GetParentResourceName()}/api/notes/save`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Meet at the pier' }),
});

if (!res.ok) console.warn('save failed', res.status, await res.text());
```

> **Always answer with a table** — A known report on the FiveM tracker shows the game crashing when a raw callback answers with an empty string (`cb('')`). Always pass a table with at least a `status` or a `body`. Remove handlers you no longer need with `UnregisterRawNuiCallback(name)`.

Raw callbacks are worth it when the page sends something that is not JSON (plain text, a large base64 string you want to pass through untouched), when you want real HTTP status codes so `res.ok` means something, or when one handler should serve a small set of routes. For ordinary buttons and forms, the normal callback is simpler.

## Never let a callback hang the UI

Even with perfect code, a Lua handler that waits on the server can take a while, and a UI that freezes on a pending promise feels broken. Give every call a timeout on the page side so the interface can recover and show an error.

*nui.ts*

```ts
export async function fetchNui<T>(name: string, data: unknown = {}, timeoutMs = 8000): Promise<T> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch(`https://${resourceName}/${name}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=UTF-8' },
      body: JSON.stringify(data ?? {}),
      signal: controller.signal,
    });
    return (await res.json()) as T;
  } finally {
    clearTimeout(timer);
  }
}
```

Pair it with a disabled state on the button that triggered it, so impatient players cannot fire the same purchase five times while the first one is still in flight.

## What the URL does not protect

The fact that the URL contains your resource name is routing, not authentication. Anyone with the devtools open can call any of your callbacks with any body. Your Lua handler should check types and ranges, and anything involving money, items or permissions must be decided on the server. The client handler’s job is to pass along what the player chose, never what it costs.

Loading screens are the exception to all of this: a `loadscreen` page cannot call callbacks at all, because no Lua is running yet for it to talk to. It only receives events and [handover data](https://fam-tools.com/guides/fivem-loading-screen-handover-data).

## Testing callbacks from the devtools console

You do not need to click through the UI to test a handler. With the game running, open the page in the devtools at `localhost:13172` and call the callback directly from the console. Because the console runs inside the NUI frame, `GetParentResourceName()` works there exactly as it does in your code.

*devtools console*

```js
await fetch(`https://${GetParentResourceName()}/buyItem`, {
  method: 'POST',
  body: JSON.stringify({ item: 'water', amount: -5 }),
}).then((r) => r.json());
```

This is also the fastest security review you can do: send the values a cheater would send (negative amounts, huge numbers, item names that do not exist, missing fields) and watch what your handler and the server do with them. If any of those calls changes something it should not, fix the server-side check before the release, not after the first exploit report.

## Questions

### What does GetParentResourceName return?

The name of the resource that owns the NUI page, exactly as the server owner named its folder.

### Why is GetParentResourceName not defined?

You are running the page in a normal browser or in a context where FiveM did not inject it. Guard it and fall back to mock data during development.

### Why does my NUI fetch never resolve?

Either `cb` was not called on some path, or the body was not valid JSON, in which case the handler never runs. Always send `JSON.stringify(...)`.

### What is the difference between RegisterNUICallback and RegisterNuiCallback?

The first is an older Lua helper built on `__cfx_nui:` events, the second registers the function directly. From the page they behave the same.

### When should I use RegisterRawNuiCallback?

When you need the raw body, the request path or headers, or want to return your own HTTP status codes.

## Related

- https://fam-tools.com/guides/fivem-nui-callbacks-fetch
- https://fam-tools.com/guides/fivem-sendnuimessage-patterns
- https://fam-tools.com/guides/fivem-nui-basics
- https://fam-tools.com/guides/fivem-nui-devtools-debugging
