Skip to Content
ScriptingLua Basics
Open in ChatGPTOpen in Claude

Start with a complete FiveM Lua resource

FiveM uses CfxLua, a modified Lua 5.4 runtime with additions such as vectors and compile-time hashes. The official first-script guide  says Lua 5.3 support was deprecated in June 2025. A new resource does not need a lua54 'yes' opt-in. Client and server scripts run in separate contexts; a local variable in one is not shared with the other.

Create the manifest

On a private development server, create resources/[local]/docs_lua_basics/. Save fxmanifest.lua:

fx_version 'cerulean' game 'gta5' client_script 'client.lua' server_script 'server.lua'

Save the following two scripts in the same resource folder. Do not store secrets in client or shared scripts: players receive those files.

Client: local values, functions and coordinates

In client.lua:

local settings = { label = 'Position', enabled = true } local function formatPosition(coords) return ('%s: %.2f, %.2f, %.2f'):format( settings.label, coords.x, coords.y, coords.z ) end AddEventHandler('docs_basics:position', function(message) print(message) end) RegisterCommand('docscoords', function() if not settings.enabled then return end local ped = PlayerPedId() if ped == 0 or not DoesEntityExist(ped) then return end local coords = GetEntityCoords(ped) TriggerEvent('docs_basics:position', formatPosition(coords)) end, false)

settings is a table; formatPosition is a local function returning a string. PlayerPedId() returns a ped entity handle, not a player server ID. GetEntityCoords returns a vector with x, y and z. Read vector operations and ID conversions before sending identifiers between contexts.

The event carries a defined string and stays local to the client. AddEventHandler is sufficient here. Register a network event only when network transport is actually needed, and validate privileged requests on the server; a different event name does not make client input trustworthy.

Server: observe a separate context

In server.lua:

RegisterCommand('docsserver', function(playerSource) if playerSource ~= 0 then return end print('[docs_basics] Server console command received.') end, true)

This example permits only the server console. A connected player must not be able to invoke administrative logic just because they know the command name. For actual client-to-server requests, follow event routing and server validation.

Start, run and restart

In the server console, enter refresh, then ensure docs_lua_basics. Join and spawn. Run docscoords in the client’s F8 console: expect a Position: line with that client’s ped coordinates. Run docsserver in the server console: expect the fixed server message. The same player’s F8 command must not execute the server-only diagnostic.

After changing files, use restart docs_lua_basics or ensure docs_lua_basics in the server console. If it cannot find the resource, check folder nesting, filename case and the active server-data path before changing caches. See resource startup errors.

Yield inside long-running threads

A loop that never yields can freeze its runtime. A periodic observation can use a deliberate interval:

CreateThread(function() while true do Wait(1000) -- Periodic non-frame-critical work belongs here. end end)

This is a scheduling fragment, not a way to slow every draw/input loop. Frame-dependent natives may require Wait(0); choose work frequency from the feature’s requirements and measure it. Wait yields a coroutine; it is not a thread-safe substitute for handling resource/session lifetime across asynchronous work.

Continue in order: first resource with bounded vehicle loading, threads, timers and async source lifetime, events, then secure events. The official CfxLua manual  remains the reference for runtime extensions.