Most FiveM "tutorials" skip this entirely, and the official debugging page is basically empty. That's exactly why we teach it early: the developers who can read an error fix in two minutes what config-editors stare at for two hours. This is the skill that separates the two.
The mindset shift
A red error in your console is not the game yelling at you. It's the game telling you the file and the line where it broke and what went wrong. Beginners panic and start randomly changing things. Developers read the message. By the end of this lesson you'll read the message.
We are also going to retire the habit everyone teaches by accident: spraying print("here1"), print("here2") through your code and guessing. You'll use it deliberately, not as a crutch.
Where errors actually show up
FiveM has three windows, and knowing which one to look at is half the battle.
1. The F8 client console. Press F8 in-game. This shows errors from client-side scripts (anything the player's machine runs — UI, drawing, key presses). If your script does something visual and nothing happens, F8 first.
2. The server console. This is your server.cfg window, or the Live Console in the txAdmin panel. It shows errors from server-side scripts (money, database, validation) and resource start-up problems. If a whole resource fails to start, you'll see it here as it boots.
3. The log file — CitizenFX.log. On the client it lives at %localappdata%\FiveM\FiveM.app\logs\CitizenFX.log. It's the full history when an error scrolled past too fast. You rarely need it, but when you do, it's everything.
Rule of thumb: client problem → F8. Server/start-up problem → server console.
(Official references, worth bookmarking: the Cfx.re Debugging page and Console Commands page — linked in the resources list, not reproduced here.)
How to read a FiveM error line
Every error has the same anatomy. Here's a real one:
[ script:myjob] SCRIPT ERROR: @myjob/client.lua:14: attempt to concatenate a nil value (global 'nme')
Read it left to right:
[script:myjob]— which resource broke (myjob).@myjob/client.lua:14— the exact file and line. Go there first, always.attempt to concatenate a nil value— what went wrong (you tried to join text onto something that'snil).(global 'nme')— the culprit's name. Something callednmeisnil.
Four facts, handed to you for free: which resource, which line, what kind of error, and the name involved. You almost never have to "search" for a bug — the error already located it.
The five errors you'll actually hit
You'll see these constantly. Learn them once and most of your debugging is just pattern-matching.
1. attempt to concatenate a nil value (global 'x') You joined text onto a variable that's nil — usually a typo. 'Hello ' .. nme when the variable is actually name. Fix: check the spelling of the name in the parentheses.
2. attempt to call a nil value (field 'x') You called something that doesn't exist — often a framework function that's spelled wrong or not loaded yet (e.g. AI loves to invent lib.notifyPlayer, which isn't real; the function is lib.notify). Fix: verify the function exists in the real docs.
3. '}' expected (to close '{' …) — a syntax error on resource start Ninety percent of the time this is a missing comma in a config table. The error line points near where Lua got confused. Fix: look for the line above the reported one and add the missing comma.
4. Your event does nothing and there's no error You used TriggerServerEvent but never RegisterNetEvent on the server (or vice-versa), so the message is sent into the void. No crash, just silence. Fix: make sure both sides exist and the event names match exactly.
5. Your NUI button does nothing The classic. Usually the callback URL doesn't match the resource name, the JS/HTML file isn't declared in files{} in your manifest, or you never released focus with SetNuiFocus(false, false). Fix: check all three — and this is exactly what the PlayDeck sandbox lets you test without booting the game.
A repeatable method (not scatter-print)
When something breaks, run this loop:
- Reproduce it. Make the bug happen on purpose so you can tell when it's fixed.
- Locate it. Read the error — it gave you the resource and line. Open that line.
- Form one hypothesis. "
nmeis nil because I typo'dname." One guess, not five. - Change one thing. Test it. If it didn't fix it, change it back. Never stack five changes — you won't know which one worked.
- Confirm. Re-run your reproduction from step 1.
When you do use print(), use it to answer your hypothesis, not to wander: print('name is:', name) right before line 14 tells you instantly whether name is nil. One targeted print beats ten scattered ones.
Practice: fix this script
Drop this into the PlayDeck sandbox (no FiveM needed) and fix the three bugs. The console will point you at each one in turn — read the line, form a hypothesis, change one thing.
-- client.lua — a simple greet command (3 bugs)
local Config = {
greeting = "Hello"
maxLength = 20
}
RegisterCommand('greet', function()
local playerName = GetPlayerName(PlayerId)
print(Config.greeting .. ', ' .. nme)
end)
Stuck? The three bugs (open after you've tried)
- Missing comma after
greeting = "Hello"→ syntax error on start ('}' expected). PlayerIdis a function — it needs to be called:GetPlayerName(PlayerId()). As written you pass the function itself, soplayerNamecomes back wrong/nil.nmeis a typo forplayerName→attempt to concatenate a nil value (global 'nme').
Recap
The console isn't noise — it hands you the resource, the line, the error type, and the name involved. Client bug → F8; server/start-up bug → server console; full history → CitizenFX.log. Learn the five common errors and most debugging becomes pattern-matching. And work the loop — reproduce, locate, one hypothesis, one change, confirm — instead of guessing. Next module, we'll put this to work the moment our scripts start talking to a database.