Every docs URL answers with Markdown when you add .md. Site map: llms.txt
Scope, files and layout
Keep FiveM Lua local by default: the visibility ladder from local function to net event, splitting client and server files, naming rules and manifest load order.
这些文档目前是英文的。
Scope is the cheapest quality decision you will make. Something that is local cannot be called by accident, cannot be overwritten by another file, and is read out of a register instead of a hash table. Something registered as a net event can be called by any player on your server.
The rule is short: give every value and every function the smallest reach that still works, and widen it only when you have a reason.
The visibility ladder
Each rung is easier to call than the one above it, and each rung is one more thing you have to keep correct.
| Rung | Who can call it | Use it when |
|---|---|---|
local function |
This file only | Default. Most helpers never need more. |
Resource global (function Foo()) |
Any file in this resource, same side | Two files in your resource need it. |
exports('Foo', fn) |
Any other resource, same side | You are deliberately offering an API. |
AddEventHandler |
Anything in the same side that triggers the event | Several handlers should react to one thing. |
RegisterNetEvent |
The other side of the network, meaning players | Data genuinely has to cross client and server. |
Only the last rung is a security decision, and it is the one people reach for first. If a client never needs to trigger it, do not register it as a net event. See Events that stay cheap and safe.
Local is also faster
In Lua 5.4 a local variable lives in a register on the call stack. A global is a key in the _ENV table, so every read is a hash lookup:
-- Every use of `Config` here is a table lookup in _ENV
for i = 1, #Config.Shops do
doSomething(Config.Shops[i])
end
-- One lookup, then registers
local shops = Config.Shops
for i = 1, #shops do
doSomething(shops[i])
endThe difference per access is tiny. In a loop that runs thousands of times it stops being tiny, and the second version reads better anyway.
Note
Resources are isolated from each other, but files inside one resource are not. A global set in client/main.lua is visible in client/shops.lua on the same side, and a global set on the server never reaches the client. local at the top of a file is visible to that file only.
Split client and server
Put client code and server code in their own folders, and share only what genuinely belongs to both:
my_resource/
├─ fxmanifest.lua
├─ shared/
│ └─ config.lua
├─ client/
│ ├─ main.lua
│ └─ shops.lua
└─ server/
├─ main.lua
└─ money.luafx_version 'cerulean'
game 'gta5'
shared_scripts { 'shared/config.lua' }
client_scripts { 'client/main.lua', 'client/shops.lua' }
server_scripts { 'server/main.lua', 'server/money.lua' }This stops the classic accident of a “shared” file that quietly ships server logic, prices or checks to every player’s PC. Anything in shared_scripts is downloaded by the client. Treat it as public.
Scripts load in the order the manifest lists them, so a file level local declared in client/main.lua does not exist while client/shops.lua is loading. If two files need the same helper, that is the signal to make it a resource global or move it into a module.
Hide helpers in their own file
When one real function needs five helpers that nobody else will ever call, the helpers do not belong next to the rest of your code. Put the group in its own file and expose one entry point:
local function basePrice(item) ... end
local function jobDiscount(price, job) ... end
local function taxFor(price, zone) ... end
--- Final price for one item, for one player.
function GetItemPrice(item, job, zone)
local price = basePrice(item)
price = jobDiscount(price, job)
return taxFor(price, zone)
endEverything above GetItemPrice is unreachable from anywhere else, which is exactly what you want. The file is now the module boundary, and the rest of the resource only knows the one name.
Order inside a file
Two layouts work, and mixing them is what makes a file hard to read. Pick one per file:
- By construct: file locals at the top, then local functions, then resource globals and exports, then event handlers and threads at the bottom.
- By call order: each single use helper sits immediately above the function that calls it, so the file reads top to bottom like the code runs.
The first suits files with a lot of shared state, the second suits a file that is one feature from start to finish.
Naming
Resource folders: lowercase, words separated by underscores, no spaces. A dash is legal but it forces the bracket form everywhere the resource is called:
exports.my_resource:GetItemPrice(item, job, zone) -- underscore, the dot form works
exports['my-resource']:GetItemPrice(item, job, zone) -- dash, bracket form requiredFrameworks like qb-core use dashes for historical reasons and everyone lives with the brackets. For a new resource, use underscores and save yourself the noise. Prefix your own resources with something short (fm_, abc_) so they group together in the resources folder and never collide with a release you install later.
Files: lowercase, no spaces, dashes or underscores between words. Linux servers are case sensitive, so Client/Main.lua in the manifest and client/main.lua on disk will work on your Windows dev machine and fail in production. This is one of the most common “works locally” bugs in FiveM.
Next: Functions that stay small.
