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.