Talking to the server
Ask the server and wait for its answer, run server actions with translated refusals, listen to server events and answer server callbacks.
A client script never trusts itself: it asks the server, and the server decides. These helpers carry those questions and answers. The server halves are on Client input (onClientCallback, onClientEvent, triggerClientCallback).
| You want to | Use |
|---|---|
| Run something the server must allow (buy, rent, open) and show its refusal | callAction |
| Ask the server for data | triggerServerCallback |
| Hear something the server sends | onServerEvent |
| Answer a question the server asks this client | onServerCallback |
triggerServerCallback
Asks the server and waits for its answer. The server answers with onClientCallback(name, ...). Use it to read data the server keeps (a price list, a player's own records). For an action that can be refused, use callAction: it shows the refusal for you.
Side: Client · Waits: yes (Promise in TypeScript, yields the thread in Lua)
triggerServerCallback(name: string, args?: unknown[], timeoutMs?: number): Promise<unknown>local result, err = aiLib.triggerServerCallback(name, args, timeout) -- call inside a threadParameters
Prop
Type
Returns
TypeScript: Promise<unknown>. It resolves with what the server handler returned (null when it returned nothing). The value is not checked: test its type before you use it. The promise rejects when the server handler threw, the server gate refused the call (rate limit, a guard, bad arguments, a lock), or no answer came within timeoutMs.
Lua: two values. result is the answer; err is nil on success, 'failed' when the server refused or failed, 'timeout' when no answer came in time. On an error result is nil.
Example
import { createLocale, notify, triggerServerCallback } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
RegisterCommand(
"mycash",
async () => {
const cash = await triggerServerCallback("myscript:cash").catch(() => null);
if (typeof cash !== "number") return notify(Locale("ERROR"), "error");
notify(Locale("YOUR_CASH", cash), "info");
},
false,
);import { onClientCallback, resourceExport } from "@aicore-fivem/lib/server";
const core = resourceExport("ai-core");
onClientCallback("myscript:cash", (source) => core.GetMoney(source, "cash"));In TypeScript always handle the rejection (.catch(() => null) or try): a refused or timed out call rejects. The server limits each callback to 20 calls per second per player by default, so never call it in a loop or every frame. The wire format is the same in both languages: a Lua client can call a TypeScript server and the other way round.
callAction
Runs an action on the server and shows the refusal for you. The server handler (onClientCallback) answers an ActionResult: { ok: true, ...data } when it worked, or fail("LANG_KEY") when it refused. On a refusal, a timeout or a gate refusal, callAction looks the error key up in your resource's lang file and shows it as an error notification. Use it for every button that changes something: buy, sell, rent, repair.
Side: Client · Waits: yes (Promise in TypeScript, yields the thread in Lua)
callAction(name: string, args: unknown[] | undefined, options: CallActionOptions): Promise<({ ok: true } & Record<string, unknown>) | null>local reply = aiLib.callAction(name, args, options) -- table or nil, call inside a threadParameters
Prop
Type
CallActionOptions
Prop
Type
Returns
Promise<({ ok: true } & Record<string, unknown>) | null>: the whole ok reply (with any extra fields the server added) when the action worked; null when it was refused, failed or timed out (the notification is already shown unless silent). The extra fields are not checked: test the type of each one you read. Lua returns the reply table or nil.
Example
import { callAction, createLocale, notify } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
export async function buy(itemId: string): Promise<void> {
const reply = await callAction("myshop:buy", [itemId], { fallback: "BUY_FAILED" });
if (!reply) return; // the refusal is already on screen
const paid = typeof reply.paid === "number" ? reply.paid : 0;
notify(Locale("BOUGHT", paid), "success");
}import { inventory, onClientCallback, resourceExport } from "@aicore-fivem/lib/server";
import { fail, oneArg, readText } from "@aicore-fivem/lib/shared";
const core = resourceExport("ai-core");
const PRICES: Record<string, number> = { water: 5, bread: 8 };
onClientCallback(
"myshop:buy",
{ args: oneArg((v) => readText(v, 32)) },
(source, item) => {
const price = PRICES[item];
if (price === undefined) return fail("UNKNOWN_ITEM");
if (!inventory.canCarryItem(source, item, 1)) return fail("NO_ROOM");
if (!core.RemoveMoney(source, "cash", price, "shop")) return fail("NO_MONEY");
inventory.addItem(source, item, 1);
return { ok: true, paid: price };
},
);The error key is looked up in the lang file of the resource that calls callAction, so put every key the server can answer (NO_MONEY, NO_ROOM, UNKNOWN_ITEM) and the fallback key in that resource's locales/en.json. A key missing there is shown as the key itself.
isActionOk
Tells whether a reply is an ok ActionResult (ok is exactly true). Use it when you call triggerServerCallback yourself and read the reply by hand.
Side: Client and server (from /shared, re-exported by /client) · Waits: no
isActionOk(reply: unknown): reply is { ok: true } & Record<string, unknown>No Lua twin: test type(reply) == 'table' and reply.ok == true.
Parameters
Prop
Type
Returns
boolean: true when reply is an object (not an array) whose ok is true. In TypeScript it also narrows reply to { ok: true } & Record<string, unknown>.
Example
import { isActionOk, replyError, triggerServerCallback } from "@aicore-fivem/lib/client";
export async function buyWater(): Promise<void> {
const reply = await triggerServerCallback("myshop:buy", ["water"]).catch(() => null);
if (isActionOk(reply)) console.log("bought");
else console.log("refused with", replyError(reply, "BUY_FAILED"));
}replyError
The lang key of a refused reply: its error field, or fallback when the reply is missing or has no error text. Use it with isActionOk when you show the error your own way (in a NUI page instead of a notification).
Side: Client and server (from /shared, re-exported by /client) · Waits: no
replyError(reply: unknown, fallback: string): string | nullNo Lua twin.
Parameters
Prop
Type
Returns
string | null: null when the reply is ok; the reply's error when it is a non-empty string; otherwise fallback.
Example
import { replyError, triggerServerCallback } from "@aicore-fivem/lib/client";
export async function buyFromPage(item: string): Promise<void> {
const reply = await triggerServerCallback("myshop:buy", [item]).catch(() => null);
const error = replyError(reply, "BUY_FAILED");
if (error) SendNUIMessage({ action: "error", data: error });
}onServerEvent
Listens to a net event the server sends with emitNet (TriggerClientEvent in Lua). Calls that did not come from the server (another resource's local emit) are dropped, so a cheat menu cannot fake them. Every ai-* client listens to the server this way, never with a bare onNet. In TypeScript a handler that takes arguments must name an args parser: it checks the raw values, and null drops the call.
Side: Client · Waits: no
onServerEvent(name: string, handler: () => unknown): void
onServerEvent<A extends readonly unknown[]>(name: string, options: { args: (raw: unknown[]) => A | null }, handler: (...args: A) => unknown): voidaiLib.onServerEvent(name, handler) -- handler(...) gets the raw argumentsParameters
Prop
Type
Returns
void.
Example
import { onServerEvent, Points } from "@aicore-fivem/lib/client";
let renting = false;
const points = new Points();
// No arguments: the short form.
onServerEvent("myscript:reset", () => points.clear());
// One boolean argument: parse it, null drops a bad call.
onServerEvent(
"myscript:state",
{ args: ([active]) => (typeof active === "boolean" ? [active] : null) },
(active) => {
renting = active;
points.refresh();
},
);The server sends the event with emitNet("myscript:state", source, true) (TypeScript) or TriggerClientEvent('myscript:state', source, true) (Lua). Lua handlers get the raw arguments: check their types yourself.
fromServer
Whether the net event being handled right now came from the server. onServerEvent already uses it; you need it only in a bare onNet handler you cannot replace.
Side: Client · Waits: no
fromServer(): booleanNo Lua twin: in a Lua net event handler, source is empty for local emits.
Returns
boolean: true when the current event carries the server's numeric source; false for a local emit by any resource.
Example
import { fromServer } from "@aicore-fivem/lib/client";
onNet("legacy:event", (value: unknown) => {
if (!fromServer()) return; // a local emit, not the server
console.log("server said", value);
});Read it before the first await of the handler: after an await the current event is gone and the answer is wrong.
onServerCallback
Answers a question the server asks this client with triggerClientCallback(name, source, ...). The value your handler returns (or its promise resolves to) is sent back. A thrown error answers a failure, and the server's promise rejects.
Side: Client · Waits: no (the handler may be async)
onServerCallback(name: string, handler: (...args: unknown[]) => unknown): voidaiLib.onServerCallback(name, handler) -- the handler's return value is the answerParameters
Prop
Type
Returns
void.
Example
import { onServerCallback, player } from "@aicore-fivem/lib/client";
// The server asks how far this player stands from a point.
onServerCallback("myscript:distance", (x, y, z) => {
if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number") return null;
return player.distanceTo({ x, y, z });
});In Lua a handler that returns false answers nil (the server gets null): return true / nil, a number or a table when the server must tell false from "no answer". The answer comes from the player's computer, so a cheater can change it. Use client answers for things only the client knows (what is on screen, a local entity), and never as proof for money or items.