TUTORIALLUADEVELOPMENTBEGINNERSCODINGHOW-TOSCRIPTING BASICSFIVEM SCRIPTINGLEARN LUAFIRST SCRIPT March 1, 2026 · 8 min read

FiveM Lua Scripting — Write Your First Script

You want to make your own FiveM scripts instead of relying on what other people build. Good. That’s how I started too, and honestly it’s not as hard as it looks from the outside.

This guide assumes you know basically nothing about Lua or FiveM scripting. By the end, you’ll have a working script that does something visible in-game, and you’ll understand enough to start modifying other people’s scripts or building your own.

What You Need

  • A FiveM server — even a local one is fine for testing. See my server setup guide if you don’t have one.
  • A text editor — VS Code is the standard. Install the “FiveM Lua” or “sumneko Lua” extension for syntax highlighting and autocomplete.
  • Patience — your first script will probably break. That’s normal. That’s how you learn.

Lua in 5 Minutes

Lua is the scripting language FiveM uses. If you’ve used JavaScript or Python, Lua will feel familiar. If you haven’t programmed before, don’t worry — Lua is one of the simpler languages to pick up.

Here’s the stuff you need to know right now:

-- Variables
local name = "YBN"          -- text (string)
local health = 200          -- number
local isAlive = true        -- boolean (true/false)

-- If statements
if health > 100 then
    print("Healthy")
elseif health > 0 then
    print("Hurt")
else
    print("Dead")
end

-- Loops
for i = 1, 10 do
    print(i)  -- prints 1 through 10
end

-- Tables (Lua's version of arrays/objects)
local fruits = {"apple", "banana", "orange"}
print(fruits[1])  -- "apple" (Lua starts at 1, not 0)

local player = {
    name = "John",
    job = "police",
    money = 5000
}
print(player.name)  -- "John"

-- Functions
local function greet(playerName)
    return "Hello, " .. playerName  -- .. concatenates strings
end

print(greet("John"))  -- "Hello, John"

That’s genuinely enough Lua to start writing FiveM scripts. You’ll pick up the rest as you go.

How FiveM Scripts Work

Every FiveM script (called a “resource”) is a folder with a specific structure:

my_script/
├── fxmanifest.lua    -- tells FiveM what this resource is
├── client.lua        -- runs on each player's game
└── server.lua        -- runs on the server

fxmanifest.lua

This is the manifest file — it tells FiveM what your resource contains:

fx_version 'cerulean'
game 'gta5'

name 'my_script'
description 'My first FiveM script'
author 'YBN Scripts'

client_script 'client.lua'
server_script 'server.lua'

That’s it. Not complicated.

Client vs Server

This is the most important concept to understand:

Client scripts run on each player’s computer. They handle:

  • Drawing things on screen (markers, text, UI)
  • Detecting player input (key presses)
  • Reading the player’s position, vehicle, etc.
  • Anything visual or local to one player

Server scripts run on the server. They handle:

  • Database queries
  • Money transactions
  • Inventory changes
  • Anything that needs to be authoritative (anti-cheat, validation)
  • Communication between players

The rule: Never trust the client. If a player could benefit from lying about a value (money, health, items), that logic needs to be on the server. The client can request things, but the server decides.

Your First Script: A Teleport Command

Let’s build something that actually works. We’ll make a /teleport command that moves you to a set of coordinates.

Create a new folder in your resources/ directory called my_first_script.

fxmanifest.lua:

fx_version 'cerulean'
game 'gta5'

name 'my_first_script'
description 'Teleport command tutorial'
author 'Me'

server_script 'server.lua'

server.lua:

RegisterCommand('teleport', function(source, args, rawCommand)
    -- source = the player's server ID (0 if typed in console)
    if source == 0 then
        print("This command can only be used in-game")
        return
    end

    -- Get the player's ped (character model)
    local ped = GetPlayerPed(source)

    -- Teleport to Legion Square
    SetEntityCoords(ped, 195.0, -933.0, 30.0)

    -- Let the player know
    TriggerClientEvent('chat:addMessage', source, {
        args = {"System", "Teleported to Legion Square!"}
    })
end, false) -- false = anyone can use it

Now ensure my_first_script in your server.cfg, restart, and type /teleport in chat. You’ll teleport to Legion Square.

Congratulations — you just wrote a FiveM script.

Making It Better: Multiple Locations

That was cool but kind of useless. Let’s add multiple teleport locations:

local locations = {
    ["legion"]   = {x = 195.0,   y = -933.0,  z = 30.0,  label = "Legion Square"},
    ["airport"]  = {x = -1037.0, y = -2737.0, z = 20.0,  label = "Airport"},
    ["sandy"]    = {x = 1848.0,  y = 3694.0,  z = 34.0,  label = "Sandy Shores"},
    ["paleto"]   = {x = -170.0,  y = 6336.0,  z = 31.0,  label = "Paleto Bay"},
}

RegisterCommand('tp', function(source, args, rawCommand)
    if source == 0 then return end

    local locationName = args[1]

    if not locationName then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"System", "Usage: /tp [legion|airport|sandy|paleto]"}
        })
        return
    end

    local loc = locations[string.lower(locationName)]

    if not loc then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"System", "Unknown location: " .. locationName}
        })
        return
    end

    SetEntityCoords(GetPlayerPed(source), loc.x, loc.y, loc.z)
    TriggerClientEvent('chat:addMessage', source, {
        args = {"System", "Teleported to " .. loc.label}
    })
end, false)

Now you can type /tp airport or /tp sandy. See how easy it is to extend?

Understanding Natives

“Natives” are the functions GTA V exposes to FiveM. Things like GetEntityCoords, SetEntityHealth, CreateVehicle — these are all natives. There are thousands of them.

The FiveM documentation at docs.fivem.net/natives lists them all. It can be overwhelming at first, but you only need a handful to do most things:

Common client-side natives:

  • PlayerPedId() — get your character
  • GetEntityCoords(ped) — get position
  • GetEntityHeading(ped) — get direction you’re facing
  • IsPedInAnyVehicle(ped) — check if in a vehicle
  • GetVehiclePedIsIn(ped) — get the vehicle
  • DrawMarker(...) — draw a 3D marker in the world

Common server-side natives:

  • GetPlayerPed(source) — get a player’s character on the server
  • GetPlayerName(source) — get their name
  • SetEntityCoords(entity, x, y, z) — move an entity
  • GetNumPlayerIdentifiers(source) — get player identifiers (Steam, license, Discord)

Events: Client-Server Communication

Client and server scripts talk to each other through events. This is how you send data between them:

Server → Client:

-- Server sends a message to a specific player
TriggerClientEvent('myScript:notify', playerId, "Hello from the server!")

-- Server sends to ALL players
TriggerClientEvent('myScript:notify', -1, "Server announcement!")
-- Client listens for it
RegisterNetEvent('myScript:notify')
AddEventHandler('myScript:notify', function(message)
    -- do something with the message
    print(message)
end)

Client → Server:

-- Client sends to server
TriggerServerEvent('myScript:requestMoney', 500)
-- Server listens
RegisterNetEvent('myScript:requestMoney')
AddEventHandler('myScript:requestMoney', function(amount)
    local src = source  -- always capture source in a local variable
    -- validate, check permissions, then give money
    -- NEVER trust the amount the client sends without validating it
end)

Security note: Any player with a mod menu can trigger any client event. Never do something like TriggerServerEvent('giveMoney', 999999) and have the server blindly trust it. Always validate on the server side.

Threads and Loops

FiveM uses threads to run continuous logic. This is how you check for things happening in the game world:

-- Client-side: draw a marker every frame
Citizen.CreateThread(function()
    while true do
        local sleep = 500  -- check every 500ms
        local playerCoords = GetEntityCoords(PlayerPedId())
        local markerCoords = vector3(195.0, -933.0, 29.0)
        local distance = #(playerCoords - markerCoords)

        if distance < 20.0 then
            sleep = 0  -- close enough, draw every frame
            DrawMarker(1, markerCoords.x, markerCoords.y, markerCoords.z,
                0,0,0, 0,0,0, 1.0,1.0,1.0, 255,0,0,100, false,false,2,false)

            if distance < 1.5 then
                -- player is right on top of the marker
                -- show interaction prompt, trigger something, etc.
            end
        end

        Wait(sleep)
    end
end)

Notice the sleep variable pattern — this is critical for performance. Don’t run expensive checks every frame when you only need them occasionally. I go deep on this in my performance guide.

Where to Go From Here

You now know enough to:

  • Read and understand most FiveM scripts
  • Modify configs and simple logic in existing scripts
  • Write basic scripts of your own

Next steps:

  1. Read other people’s code. Download free scripts from the CFX forums and read through them. You’ll learn patterns and tricks.
  2. Build something small. A custom command, a simple marker interaction, a notification system. Keep it small until you’re comfortable.
  3. Learn your framework’s API. If you’re on ESX, learn how ESX.GetPlayerFromId(), xPlayer.addMoney(), etc. work. The framework docs are your friend.
  4. Join developer communities. The Cfx.re Discord has a scripting channel. Our Discord has devs who can help too.

Don’t try to build a full MLO interior or a complete job script as your second project. Scale up gradually. You’ll get there faster than you think.

YBN
YBN Scripts
FiveM script developer at YBN. Building premium ESX, QBCore & Qbox resources.

Related Posts

Need scripts for your server?

Check out our premium FiveM resources — ESX, QBCore & Qbox supported.

Browse Premium Scripts → Free Scripts →