Skip to content

Exports

Every Lua export tLib registers, which side it lives on, and what it returns.

Call every export as exports['tLib']:FunctionName(...). Each section says which side it is registered on.


Client and server. One NUI menu shared by every resource. tLib owns the screen, the keyboard and the permission check; you declare pages and handle rows.

Pages register on the server, and every open is answered with that player's ACE-filtered tree. Handlers, presenters and hooks register on the client. Menu IDs are namespaced owner:name. Every export takes an optional trailing declaredOwner, used only where the platform cannot identify the caller.

Menu Consumers builds one end to end.

Server — declaring the tree

ExportDescription
DefineMenuNamespace(spec)Required first. { ace, catalog?, order?, capabilities?, theme? }; ace gates the namespace.
RegisterMenuDomains(domains)Rate/cooldown domains for actions.
RegisterMenu(menu, definition)One page plus its authority.
UnregisterMenu(menuId)Remove a page.
RegisterMenuAuthority(spec) / UnregisterMenuAuthority()Answer for rows you do not own.
OnMenuSnapshot(hook)Adjust a player's tree as it is built.
RefreshMenus()Re-push every open menu.

definition is { ace, actions = { [itemId] = { ace, run?, clientAction?, cooldown?, fixedValue?, domain? } }, theme?, rootVisible? }. rootVisible = false omits the shared-root link only; it changes no permission.

Server — changing what is registered

ExportDescription
PatchMenuItem(menuId, itemId, changes)Patch one row.
PatchMenuAction(itemId, changes)Patch one action; finds the page itself.
SetMenuLinks(menuId, links)Replace dynamic submenu rows on your page. {} removes them.
SetMenuItemVisible(itemId, visible)Show or hide a row.
SetPlayerMenuOptions(source, itemId, options)One player's choices for a selector.
SetMenuNamespaceTheme(theme) / SetMenuTheme(menuId, theme)nil clears.

Client — behaviour

ExportDescription
RegisterMenuActions(handlers){ [name] = function(value, context) end }. Installed whole — all or none.
RegisterMenuCatalogProvider(provider)Build the catalog once, off a function that may yield.
RegisterMenuHooks(spec){ snapshot, enter, leave, page, selection, close }. enter may refuse a navigation.
RegisterMenuPresenter(spec) / UnregisterMenuPresenter()Fill rows in as a page is presented.
RegisterMenuStrings(dictionaries)Translations for your rows.
ConfigureMenuInterface(spec)The root page. One screen, so the last caller wins.
SetMenuEffect(name, enabled, definition)A handler that keeps running after activation, re-checked against the player's grants.
PublishMenuItem(name, changes)Patch a row from the client.
SetMenuItemState(name, value) / GetMenuItemState(name)Read and write a row's value.
ExecuteMenuAction(name, value, context)Run an action as if the row was activated.
OnMenuActionsCleanup(fn) / OnMenuActionsPersist(fn)Resource-stop hooks.

Client — the screen

Unscoped: the screen belongs to the player.

ExportDescription
OpenMenu(menuId?)Re-checks against a fresh snapshot. Refuses while a menu is up.
CloseMenu()The close key's teardown. No toggle: closing nothing answers false, 'Menu is not open'.
IsMenuVisible(), GetMenuStackIds()Current state.
NavigateMenu(menuId), InvalidateMenuContext()Move within the open tree.
BindMenuKey(menuId)Claim a key-binding slot; returns the command it runs under.
SetMenuLocale(tag) / GetMenuLocale()Row translation locale.
SetMenuAnchor(anchor), SetMenuPalettes(value)Placement and colour.
PromptMenuText(title, initial, maximum, context)The game's onscreen keyboard. Only inside an action handler, whose context it needs.
SubmitFeedback(message, callback)Send one player message to TIMMYG Studios.

SubmitFeedback

For a resource whose settings live in a tLib dialog. A menu already grows a "Send feedback" row on its owner's root page.

lua
local sent = exports['tLib']:SubmitFeedback(message, function(result)
    if result.ok then return end
    -- result.error == 'rate-limited' | 'invalid' | 'unreachable'
end)
AttributionThe calling resource, resolved by the runtime. It cannot be sent in another's name.
callback{ ok, error? }; error is invalid, rate-limited or unreachable.
Returnfalse, reason on a malformed call — empty message, or no callback.
LimitsOne per player per minute, six per server per minute, shared with the menu row. The endpoint counts a game server as one IP.

A page

lua
{ id = 'myres:tools', title = 'Tools', subtitle = ..., description = ..., parent = ..., items = { ... } }

A row

kind is action, toggle, select, slider, submenu or status.

KeyTypeDescription
idstringowner:name, permanent: what the server authorizes and what state is stored under.
label / descriptionstringThe row text, and the line under the panel.
valueboolean|string|numberA toggle's state, a slider's position.
options / indextable / numberA selector's choices, and the current one.
min / max / stepnumberA slider's bounds.
menustringA submenu row's destination.
confirmstringAsk this before the row runs.
icon / rightIcon / rightLabelstringRow decoration.

Every kind on one page, in that order:

A namespace or page theme reskins the same definition:


Dialog System

Client. Modal form dialogs. Results arrive as local events and as optional inline callbacks — additive, not alternatives.

ExportDescription
ShowDialog(id, opts)Open one. Returns the resolved id, or nil, reason.
CloseDialog(id)Close it; fires the cancelled event.
UpdateDialogField(id, fieldId, changes)Patch one field of a live dialog.
UpdateDialogFields(id, changes)Patch several at once.

A default md dialog, with a required text, a two-option select as a segmented control and a checkbox:

ShowDialog options

KeyTypeDefaultDescription
titlestring—Dialog heading.
descriptionstring—Subheading.
submitLabelstring"Submit"Submit button label.
cancelLabelstring"Cancel"Cancel button label.
sizestring"md""sm", "md" or "lg".
themestring—Id of a registered tLib theme.
blurbooleanfalseBlur the scene behind the dialog.
resetStatebooleanfalseOpen at the defaults, not where the player left it.
fieldstable[]—Field definitions.

A dialog reopens where the player left it — selected tab, scroll position, dragged panel position — per dialog id. Values and validation state are never kept, and a tLib restart or a reconnect forgets the rest. resetState = true opts out.

Field types

TypeExtra keys
text, number, passwordplaceholder, defaultValue, maxLength, min/max (number only)
textareaplaceholder, defaultValue, maxLength, rows (default 3)
select, dropdown, radiooptions ({ value, label }[]), placeholder, defaultValue
slidermin, max, step, defaultValue
checkboxdefaultValue (boolean)
buttonvariant (default / primary / destructive), confirm
displaynone

All share id (required), type, label, description, required, disabled. An unknown type logs a warning and falls back to text.

maxLength is enforced by the page, so a server-side cap is visible while typing rather than on submit.

display

Text, not a control: the label renders at full opacity with no input beside it, carries no value and is never submitted. Use it for a readout, a section heading or help text.

lua
{ id = 'note', type = 'display', label = 'Patterns are saved per vehicle model.' }

Confirming a dialog button

A button fires onButtonClick on press. Give it a confirm string and the press asks that question first; only an explicit Confirm fires the callback. Escape and Cancel answer it without firing, and the dialog stays open either way. variant = 'destructive' is colour only, so a button that cannot be undone wants both:

lua
{
    id = 'wipe',
    type = 'button',
    label = 'Clear all patterns',
    variant = 'destructive',
    confirm = 'Every pattern on this vehicle is deleted. This cannot be undone.',
}

One of each type in an lg dialog. A select with two options renders as a segmented control:

Events

tLib:dialog:opened (dialogId) · tLib:dialog:submitted (dialogId, values), keyed by field id · tLib:dialog:cancelled (dialogId)

lua
local dialogId = exports['tLib']:ShowDialog('my_dialog', {
    title = 'Player Info',
    fields = {
        { id = 'name', type = 'text', label = 'Name', required = true },
        { id = 'age', type = 'number', label = 'Age', min = 1, max = 120 },
        { id = 'role', type = 'select', label = 'Role', options = {
            { value = 'civ', label = 'Civilian' },
            { value = 'leo', label = 'Law Enforcement' },
        }},
    },
})

AddEventHandler('tLib:dialog:submitted', function(id, values)
    if id == dialogId then print(values.name, values.age, values.role) end
end)

Toast / Notification System

Client. Non-interactable notifications stacked in a screen corner.

ExportDescription
ShowToast(id, toastType, title, description, duration, theme)Returns the resolved id, or nil, reason at capacity.
UpdateToast(id, changes)Merge changes into a live toast in place.
DismissToast(id)Dismiss one before it expires.
DismissAll()Dismiss this resource's visible toasts.

IDs are local to the producing resource, and stopping a producer removes its persistent cards. A type change or duration = 0 restarts the type default; text- or theme-only updates keep the deadline. Limits: 32 active per producer, 256 total.

TypeDuration
success4000 ms
error6000 ms
warning5000 ms
info4000 ms
loadingUntil dismissed or updated

Each type carries its own icon and colour. Newest sits at the bottom:

Events: tLib:toast:shown (id, toastType, title, owner) · tLib:toast:updated (id, patch, owner) · tLib:toast:dismissed (id, owner) · tLib:toast:dismissedAll (owner)

lua
exports['tLib']:ShowToast(nil, 'success', 'Saved', 'Your settings have been saved.')

local toastId = exports['tLib']:ShowToast(nil, 'loading', 'Saving...', 'Please wait.')
exports['tLib']:UpdateToast(toastId, { type = 'success', title = 'Done', description = 'Save complete.' })

Theme System

Client. CSS-variable themes, global or scoped to one dialog, toast or menu. One id themes all three surfaces.

ExportDescription
AddTheme(id, opts)Register, or replace one with the same id. true on success.
RemoveTheme(id)Unregister. true if it existed.
SetTheme(id)Apply globally. "" clears.
GetTheme(id)A copy of one definition, or nil.
GetThemes()A copy of every theme, keyed by id.
opts keyTypeDescription
namestringDisplay name.
descriptionstringOptional.
cssVarstableVariable name → value. Keys must not carry the leading --.
extendsstringTheme id to inherit from; these vars overlay it.
fontstringFont-family string; sets --font-sans.
fontSizestringBase font-size percentage, e.g. "87.5%".

Events: tLib:theme:added, tLib:theme:removed, tLib:theme:set — each (id).


HUD Layout

Client. Puts your on-screen element in the shared HUD editor, so a player moves, scales and hides it alongside every other resource's HUD.

ExportDescription
RegisterHudComponent(id, options, fn)Register a draggable component. fn(layout) runs on every layout change.
UnregisterHudComponent(id)Remove it.
GetHudLayout(id)Its current layout, or nil.
SetHudLayout(id, layout)Move your own component from code. true, or false, reason.
OpenHudEditor(options)Open the editor. { demo = true } adds a sample component.
CloseHudEditor()Close it, if this resource opened it.
ResetHudLayout(id?)Restore factory placements and clear the saved record. No id means all; returns the count.

options is { label, width, height, canHide?, canScale?, minScale?, maxScale?, persist?, minVisiblePixels?, minVisibleFraction?, defaults = { x, y, scale, visible, anchor }, layout? }. Layouts persist per server endpoint; without one they are session-only and tLib says so at startup. For a persist component the record is written before fn runs, so true means the position survived.

One editor, not one per resource:

defaults and layout are different things

defaults is the factory placement, where "Reset this" goes. layout is where the widget is now. Pass both, always:

lua
exports['tLib']:RegisterHudComponent('radio', {
    label = 'Radio', width = 300, height = 440, persist = false,
    defaults = factoryPlacement,   -- where Reset puts it
    layout   = savedPlacement,     -- where it is
}, onLayout)

With persist = false you own the storage, and passing the saved position as defaults makes Reset restore the placement the player is escaping. Pin tlib_min_version '1.0', the first release carrying layout.

Every layout tLib is handed — saved record, layout, defaults — is validated independently and falls through to the next when unusable, then clamped against the live resolution so at least minVisiblePixels (96) or minVisibleFraction (0.4) stays on screen. That runs on registration.

Units

x and y are a ratio of a 1080px-tall reference screen on both axes, the same unit the widget's size scales by. Centre-anchored x is the exception and stays proportional to width.

If a player loses their HUD, /tlibhud reset restores every component to its factory placement and clears the saved layout — editor open or shut, whichever resource opened it.


Keybind registry

Client. The only place two different resources' bindings are visible at once — a resource's own tlib.platform.keybindConflicts() sees only its own VM.

ExportDescription
ReportKeybind(entry)Record one bind: { token, resource?, label?, default?, group? }. tlib.platform.bindKey calls it for you.
GetKeybinds()Every bind so far, as { resource, token, label, default, group }.
GetKeybindConflicts(){ { group, key, claims = { … } }, … }.

Only token is required; resource defaults to ?, label to the token, group to keyboard. A malformed entry is dropped silently rather than raised, so a typo shows up as a bind missing from GetKeybinds(). An entry with no default never counts as a collision, and a conflict needs more than one resource.

tLib prints its report ten seconds after start, once; a bind reported later is recorded but misses it, so call GetKeybindConflicts() yourself after that. Nothing here reassigns a key — act on a conflict by changing a default and shipping it.


Marketplace

Client and server. The in-game panel for browsing and installing published content.

ExportSideDescription
RegisterMarketplaceType(kind, opts)ServerDeclare a content type this resource provides.
OpenMarketplace(opts)ClientThe standalone panel. { resource = 'tRadio' } scopes it; { initialTab = 'sounds' } picks the tab.
ProvideMarketplaceData(opts)ClientSame payload into a tab of a dialog you already opened. Takes no NUI focus.
CloseMarketplace()ClientClose the standalone panel.

For ProvideMarketplaceData, give your dialog a tab with view = "marketplace" and call it when the server answers with what is installed. Use OpenMarketplace where there is no host dialog to embed in.


Logger

Client and server. Levels are 1 DEBUG, 2 INFO, 3 WARN, 4 ERROR. A trailing number in the arguments is the level; everything else is tostring'd and space-joined.

ExportDescription
CreateLogger(loggerId, printFn, opts)A log function pinned to a source name. opts.minLevel overrides the global level for it alone.
Log(loggerId, args)Format and filter one line. Returns the string, or nil when filtered or empty.
SetLogLevel(level)Set the global minimum.
lua
local log = exports['tLib']:CreateLogger('myresource', print)
log('Server started')           -- INFO
log('Something went wrong', 4)  -- ERROR

-- Log() returns the line and leaves printing to you:
local line = exports['tLib']:Log('myresource', { 'Something went wrong', 4 })
if line then print(line) end

HTTP Helper

Server. Non-blocking requests through FiveM's PerformHttpRequest. Not related to tLib's public HTTP routing hub.

ExportDescription
Fetch(opts, callback)Make an HTTP or HTTPS request.
opts keyTypeDescription
urlstringRequired. Full URL including scheme.
methodstringDefault "GET".
headerstableKey/value table.
bodystringRequest body.

Redirects are followed. There are no timeout or TLS-verification options. callback is function(statusCode, body, headers, errorData). An HTTP error status is still a delivered response, so check the status.

lua
exports['tLib']:Fetch({ url = 'https://example.com/api', method = 'GET' },
    function(status, body, headers, err)
        if status ~= 200 then return print('Request failed:', status, err) end
        print('Body:', body)
    end)

Permissions

Client and server. Levels: 0 none, 1 admin (tlib.admin), 2 superadmin (tlib.superadmin).

ExportSideDescription
IsAdmin()Clienttrue if the local player is at least admin.
GetPermissionLevel()ClientThe local player's cached level.
RefreshAdminPermission()ClientRe-ask the server.
IsPlayerAdmin(playerId)Servertrue if that player is at least admin.
GetPlayerLevel(playerId)ServerThat player's numeric level.
HasPermission(source, key)ServerCheck one permission key.
HasPermissionBatch(source, keys)ServerA key list in one call. Returns { [key] = boolean }.
RegisterAdminEvent(eventName, handler)ServerA net event that only fires for admins. Denials are logged.
RegisterPermissionResolver(fn)ServerReplace ACE checks with a framework's own system.

Server. Both exports cross as a JSON string. See tlib_telemetry in Convars for what is collected.

ExportDescription
GetTelemetryConsent(){ enabled, source, locked, convar, convarValue, queued, transport }.
SetTelemetryConsent(enabled){ ok = true, consent }, or { ok = false, reason, detail }.
lua
local c = json.decode(exports['tLib']:GetTelemetryConsent())
  • There is no provisioned field; never render a "cannot transmit" state.
  • It is not permission-gated. Gate it at your own call site.
  • Never put it on an unauthenticated surface. tLib's hub binds 0.0.0.0 with no auth on its index, so it calls neither export.

tlib_telemetry_status in the server console prints the same answer plus the queue depth and whether the transport has given up.


Local inference

Server. For resources shipping their own model runtime. Everything crosses as a JSON string; an empty string is the universal "no".

ExportDescription
InferenceAdvertise(json)Register an engine you started: { capability, workload, url, model }. Returns a handle; the URL must be 127.0.0.1.
InferenceLookup(capability, workload)Another resource's engine for the same weights, or ''. Never your own.
InferenceWithdraw(handle)Drop one of yours. Stopping the resource does this anyway.
InferenceList()Everything registered, for diagnostics.

Starting those engines is a separate, gated surface — see Child processes.


Child processes

Server. One line covers a whole box, however many resources on it ship a local engine:

cfg
add_unsafe_child_process_permission tLib

tLib makes the call, so no other resource needs its own grant.

ExportDescription
SpawnServe(json)Start a long-lived process: { key, program, args, cwd, env, mode }. { pid } or { error }; a live key served twice returns the first pid.
SpawnOutput(key, since){ lines, next, alive, code } since your cursor. In bytes mode, { chunks, next, stderr, alive, code }.
SpawnUnserve(key)SIGTERM, then SIGKILL after a five-second grace.
SpawnWrite(key, base64)Write bytes to a bytes-mode process's stdin.
SpawnRun(json)A one-shot: { program, args, cwd, env, stdin }. Returns { job }.
SpawnJob(id){ state, code, stdout, stderr, error }. Reading a finished job forgets it.
SpawnCancel(id)Kill a running one-shot.
SpawnList()What is running and for whom.

cwd is resource-relative and must be one the catalog declares for that program; omitted, it is the binary's own directory. env names must be on the program's allowlist, so LD_PRELOAD cannot be set. mode is lines (default) or bytes, which returns stdout base64 with nothing dropped and keeps stdin open.

The caller names a program, never a path. tLib's catalog declares which resources may spawn, which programs each may run, where those live and what their arguments may look like. It resolves the executable inside the calling resource, rebuilds the argv from a declared shape, and execs with no shell. The catalog declares a binary per platform, so the caller never says which it wants and nothing shells out to PowerShell.

Lifecycle

A child belongs to tLib's runtime, not its owner's. tLib reaps it when the owning resource stops (including the stop half of a restart), when the owner calls SpawnUnserve, and when tLib stops. A caller restarting is kill-and-respawn, not reuse.

Binary integrity

These binaries are verified where they are fetched, not where they are run. This gate adds containment and argument shape, not integrity.


Utilities

ExportSideDescription
GenerateId()BothA unique id string; the generator behind dialogs and toasts.
Platform()ClienttLib's platform adapter as a table of callable references.
CreateDiscovery(opts)ServerScan resources for tagged config files, load and save them, and pick up late starters.
CreateServerSettings(opts)ServerAdmin-configurable settings for one resource, persisted to a JSON file inside it.
CommunityConfigsImport(...) / CommunityConfigsUpload(...)ServerCommunity configs API proxy; the consumer repeats no HTTP boilerplate and never sees the server hash.
CommunityConfigsPeek(id, cb)ServerImport's payload without incrementing the import counter. Use it to render a listing.
CommunityConfigsList(opts, cb)ServerBrowse published configs. { script, configType, key?, limit? }; first two required, limit default 30. cb(ok, items, err), items decoded.
CommunityConfigsMatch(opts, cb)ServerCandidates by model fingerprint. { fp, script?, configType?, key?, limit? }; fp required, configType default led_layout. Each item carries the publisher's own fp — comparing it is yours to do.
IsDemoMode()Servertrue while tlib_demo is on. The export behind tlib.demo.active().

CreateDiscovery needs resourceName = GetCurrentResourceName() when called at module init time: GetInvokingResource() is nil during top-level execution, and without the name the file I/O targets tLib instead of you.


Exporting a config into another resource

Discovery can move a config out of your data.json and into the resource that owns the model — a vehicle pack ships tels.json beside its .yft, declares it in its own fxmanifest.lua, and the config travels with the vehicle.

So the destination performs the write: tLib calls the target's own WriteResourceFile export. Nothing is reported as exported unless the file landed.

The target decides which path you get:

  • It runs its own code. Any resource whose server bundle calls registerWriteResourceFile() from tLib/shared/ts/fs/server.ts accepts the export directly. tLib writes the JSON in and adds the metadata line to its fxmanifest.lua.
  • It is a plain asset pack. No scripts, no bundle, no runtime to write with. tLib writes the finished file into its own directory and prints these steps to the player in chat and to the server console:
1. Copy tLib/export-staging/<target-resource>/<file>.json  ->  <target-resource>/<file>.json
2. Add this line to <target-resource>/fxmanifest.lua:  tels_config 'tels.json'
3. Restart <target-resource> (or the server).

The staged file is the target's existing config with your model merged in. Until you copy it the config is still local and still served to clients.

The metadata key is whatever the consuming resource declares — tels_config for tELS, tradio_config for tRadio — and the value is the file name relative to the resource root. Without that line the file is never read.

Still stuck? Support can answer it directly.

Was this page helpful?