If / else / elseif: how code makes decisions
So far your scripts have run from top to bottom and always done the same thing. This lesson changes that. You will teach Lua to choose: look at a value, ask a question about it, and take a different path based on the answer. This is where a simple script starts becoming a real program.
Build it
Make the resource folder
Inside your server's resources folder, create this folder:
resources/qu_if_else_elseifCreate the files
Create this exact file layout:
resources/qu_if_else_elseif/
fxmanifest.lua
server.luaWrite fxmanifest.lua
Open fxmanifest.lua and paste this:
fx_version 'cerulean'
game 'gta5'
server_script 'server.lua'Older tutorials add a lua54 'yes' line here. As of June 2025 that setting is deprecated and ignored: Lua 5.4 is now the only Lua runtime, so you leave it out.
Write the lesson code
Open server.lua and paste this:
RegisterCommand('gate', function(_, args)
local level = tonumber(args[1]) or 0
local rank
if level >= 20 then
rank = 'owner'
elseif level >= 10 then
rank = 'admin'
elseif level >= 1 then
rank = 'member'
else
rank = 'guest'
end
print(('[qu_if_else_elseif] level %d rank %s'):format(level, rank))
end, true)Start and test it
Open server.cfg and add this line:
ensure qu_if_else_elseifSave. Now type the rest in your server console - that is the txAdmin Live Console (txAdmin web panel at http://localhost:40120, left menu, Live Console) or the black FXServer.exe console window. Do NOT use the in-game F8 console or the in-game chat: the command is registered as restricted (the true at the end of RegisterCommand), so the server console runs it while a player in chat would be blocked by the command.gate permission.
In the server console, run:
restart qu_if_else_elseifThen, in that same server console, run this test:
gate 12How it works
You passed 12 and the gate answered admin. It did not say owner, and it did not say member, even though 12 would pass a couple of those tests. That precision is the whole lesson. An if chain is not magic, it is a strict reading order, and once you can predict which branch wins you can build any decision you want. Take the code apart top to bottom.
The chain reads top to bottom and stops at the first yes
This is the single most important idea in the lesson. Lua reads an if / elseif / else chain from the top, checks one condition at a time, and the moment a condition is true it runs that branch and skips everything below it. It does not keep checking. It does not pick the best match. It picks the first match.
Walk gate 12 through it. level is 12.
if level >= 20 then -- 12 >= 20 is false, skip
elseif level >= 10 then -- 12 >= 10 is true, run this, stop here
elseif level >= 1 then -- never reached
else -- never reached
endThe chain checks >= 20, gets false, drops to the next test, checks >= 10, gets true, sets rank = 'admin', and is done. The >= 1 test and the else are never even looked at. That short-circuit is why an if chain is fast and why order is everything.
Now you can see why the conditions are written from highest to lowest. A level of 25 is also greater than 10 and greater than 1, so all three conditions are true for it. If >= 1 came first, every level above 1 would print member and the higher branches would be dead code. By putting the strictest condition at the top, you let the most specific answer win before the looser ones get a chance.
If you reordered the chain so elseif level >= 1 came first, what would gate 25 print, and why?
It would print member. The chain stops at the first true condition. With >= 1 at the top, 25 >= 1 is true on the very first check, so Lua sets rank = 'member' and skips the >= 20 and >= 10 branches entirely. They become unreachable. The fix is the order in the lesson: test the strictest, highest threshold first and let it fall through to looser ones only when the higher tests fail.
Comparison operators ask the yes-or-no question
Every condition in the chain is a comparison: a question that Lua answers with true or false. The one you used is >=, greater than or equal to. Here is the full set you will reach for constantly:
The trap that bites every beginner is the difference between = and ==. A single = assigns a value: level = 10 puts 10 into the variable level. A double == asks whether two things are equal: level == 10 is a question that comes back true or false. Inside an if you almost always want ==. Writing if level = 10 then is a syntax error in Lua, which is the language doing you a favor by refusing to run it, but the habit of reading == as "is equal to" out loud will save you confusion later.
and / or / not combine conditions
One comparison answers one question. Real decisions usually need more than one, and that is what and, or, and not are for. They glue conditions together into a single true-or-false answer.
andis true only when both sides are true.level >= 1 and level < 10is true only for levels1through9.oris true when either side is true.rank == 'owner' or rank == 'admin'is true for staff of either kind.notflips a result.not isBannedis true whenisBannedis false.
You could rewrite the member branch to spell out an exact band instead of relying on fall-through:
if level >= 1 and level < 10 then
rank = 'member'
endThat condition is true only when level is at least 1 and below 10, so it captures exactly the member band without depending on the branches above it. Both styles are valid. The chain in the lesson leans on ordering; this version states the boundaries outright. Knowing both means you can read anyone's code and pick whichever is clearer for the case in front of you.
Nesting: an if inside a branch
Every branch so far has been flat - one question, one answer. But sometimes a branch needs to ask a second question that only makes sense once the first one passed. That is a nested if: an if written inside the body of another if.
Say an owner-level player must also be flagged as verified before the gate trusts them. "Verified" is a fact the server already knows about that player; it is not something the player types, so it stays a server-side value you control - never trust the client to declare its own rank. Extend the owner branch in server.lua:
RegisterCommand('gate', function(_, args)
local level = tonumber(args[1]) or 0
local verified = true -- server-side fact; in real code you'd look this up, not take it from the player
local rank
if level >= 20 then
if verified then
rank = 'owner'
else
rank = 'owner (unverified)'
end
elseif level >= 10 then
rank = 'admin'
elseif level >= 1 then
rank = 'member'
else
rank = 'guest'
end
print(('[qu_if_else_elseif] level %d rank %s'):format(level, rank))
end, true)In your server console (the txAdmin Live Console at http://localhost:40120, left menu, Live Console, or the FXServer window), run restart qu_if_else_elseif, then gate 25.
The inner if verified then only runs because the outer level >= 20 was already true. That ordering is the whole point of nesting: the second question is asked only in the case where it can matter. A guest at level 0 never reaches the verified check, so you never spend that test on someone it makes no sense for.
Nesting is powerful but it is also where end counts go wrong. Each if you open needs its own end. Here the inner if/else closes with one end, the outer if/elseif/else chain closes with one end, and the handler function closes with its own end before the ). Count them top to bottom - three ends - and if Lua reports 'end' expected (near <eof>), you are missing one of them. A flatter alternative, when the logic allows, is to fold the two questions into one condition with and: if level >= 20 and verified then reads as a single line and removes a layer of nesting. Reach for and when both questions are simple; reach for a nested if when the inner case needs its own else branch, like the owner (unverified) path above.
truthy vs falsy: what Lua treats as "yes"
A condition does not have to be a comparison. An if will accept any value and decide whether it counts as a yes. This is where Lua surprises people who came from other languages. In Lua the rule is short and absolute:
Only false and nil are falsy. Every other value is truthy.
That includes some values you might expect to be "no". The number 0 is truthy. An empty string '' is truthy. An empty table {} is truthy. Lua does not treat zero or emptiness as false the way C or JavaScript sometimes do. So this is a real bug waiting to happen:
if level then
-- runs even when level is 0, because 0 is truthy
endIf you meant "only when the player has a level above zero", if level then will not do it, because 0 passes. You have to compare explicitly with if level > 0 then. Lock this in now: the only two values that fail an if are false and nil. Everything else, including 0 and "", sails through.
A player types gate with no number. What is level, and which branch runs?
With no argument, args[1] is nil, tonumber(nil) is nil, and the or 0 fallback turns it into 0. So level is 0. None of 0 >= 20, 0 >= 10, or 0 >= 1 is true, so the chain falls all the way through to else and prints rank guest. The gate degrades gracefully to the lowest rank instead of erroring, which is exactly what the or 0 default was protecting against.
The true at the end: who is allowed to run the command
The handler is registered with RegisterCommand('gate', function(_, args) ... end, true). That last true is the restricted flag. On a server command it means a normal player needs the command.gate ACE permission before they can run gate from in-game chat; without that permission the chat call is silently denied. The server console (txAdmin Live Console or the FXServer window) is not a player, so it bypasses the check entirely - which is why your gate 12 test works from the console. (The _ first parameter is the command source; we discard it because this lesson never needs to know who called it.)
tonumber(args[1]) or 0: the line that does the most work
This one line carries the whole input safety of the resource, and it stacks two ideas you just learned.
local level = tonumber(args[1]) or 0Start on the inside. When a player types gate 12, FiveM hands your handler an args table, and args[1] is the string "12", not the number 12. Command arguments always arrive as text. You cannot compare a string against >= 20 and get a sensible answer, so tonumber converts the text into an actual number. Hand it "12" and you get 12. Hand it something that is not a number, like gate abc, and tonumber cannot convert it, so it returns nil.
Now the or 0 part. You already know nil is falsy. Lua's or has a useful trick: it returns the first truthy value it finds. So tonumber(args[1]) or 0 reads as "use the converted number, but if that came back nil, use 0 instead". When the conversion succeeds you get the number; when it fails or no argument was passed, you fall back to 0. This is the idiomatic Lua way to supply a default, and you will see something or default everywhere in real resources. Without it, gate abc would put nil into level and crash the first comparison.
If something went wrong
| Symptom | Fix |
|---|---|
Every level prints the same rank | Your elseif conditions are in the wrong order. The chain stops at the first true test, so the strictest threshold (>= 20) must come first. If >= 1 is on top it wins for everything. |
attempt to compare nil with number | You removed the or 0 fallback, so a non-numeric argument left level as nil and the >= comparison failed. Keep tonumber(args[1]) or 0 so a missing or bad argument becomes 0. |
'=' expected near '=' or unexpected symbol | You wrote a condition with a single = (assignment) where you meant == (equality). Inside an if, use == to ask whether two values are equal. |
'end' expected (near <eof>) | The if chain needs exactly one closing end, and the function needs its own end before the closing paren. Count: one end closes the chain, one end closes the handler. |
A level of 0 still triggers a branch you meant to skip | You tested if level then expecting 0 to be falsy. In Lua 0 is truthy. Compare explicitly with if level > 0 then. |
What you can do now
- Trace an if / elseif / else chain top to bottom and name which branch fires for a given value.
- Explain why the strictest condition must come first, and predict the bug when the order is reversed.
- Use the comparison operators >, >=, <, <=, ==, and ~= , and tell == (a question) apart from = (assignment).
- Combine conditions with and, or, and not to express an exact band like level >= 1 and level < 10.
- State Lua's truthiness rule: only false and nil are falsy, while 0 and the empty string are truthy.
- Read tonumber(args[1]) or 0 as convert-then-default and explain why command arguments need it.
Run it live
No server needed for this part. Edit the Lua below and press RUN to execute it in your browser on real Lua 5.4. Change the level value and watch which branch wins. Try 20, then 19, then 0, then a negative number, and confirm the chain picks the rank you predicted.
Press RUN to execute.