Copies this lesson plus 2026 ground rules (no lua54 'yes', Cfx.re Portal, correct callback signatures) as a ready-to-paste mentor prompt.
Module 01 · Lesson 03
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.
You'll build
A qu_if_else_elseif resource that prints a player's rank based on a level number you pass it.
Time
~15 minutes
You need
Lessons 01 and 02 complete. A running FiveM server. A text editor.
You'll learn
if -> elseif -> else -> comparison operators -> and / or / not -> truthy vs falsy
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.
4
Write the lesson code
The topic is now represented by runnable code.
Open server.lua and paste this:
lua
RegisterCommand('gate', function(_, args)local level = tonumber(args[1]) or 0local rankif level >= 20 then rank = 'owner'elseif level >= 10 then rank = 'admin'elseif level >= 1 then rank = 'member'else rank = 'guest'endprint(('[qu_if_else_elseif] level %d rank %s'):format(level, rank))end, true)
5
Start and test it
The expected proof appears in the correct console.
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.
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.
lua
if level >= 20 then -- 12 >= 20 is false, skipelseif level >= 10 then -- 12 >= 10 is true, run this, stop hereelseif level >= 1 then -- never reachedelse -- never reachedend
The 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.
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:
Lua comparison operators
>
Greater than. level > 10 is true for 11 and up.
>=
Greater than or equal to. level >= 10 is true for 10 and up.
<
Less than. level < 10 is true for 9 and down.
<=
Less than or equal to.
==
Equal to. Two equals signs. This is a question, not an assignment.
~=
Not equal to. Lua's not-equal is ~= , not != as in many other languages.
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.
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.
and is true only when both sides are true. level >= 1 and level < 10 is true only for levels 1 through 9.
or is true when either side is true. rank == 'owner' or rank == 'admin' is true for staff of either kind.
not flips a result. not isBanned is true when isBanned is false.
You could rewrite the member branch to spell out an exact band instead of relying on fall-through:
lua
if level >= 1 and level < 10 then rank = 'member'end
That 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.
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:
lua
if level then -- runs even when level is 0, because 0 is truthyend
If 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.
This one line carries the whole input safety of the resource, and it stacks two ideas you just learned.
lua
local level = tonumber(args[1]) or 0
Start 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.
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.
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.