The Book·Chapter 1/12·9 min

How to make scripts in FiveM

From zero to your first working command: resources, fxmanifest, basic Lua, the client-server model and the network events that tie it all together.

Making scripts in FiveM is simpler than it looks from the outside. If you grasp four ideas (what a resource is, how it's declared, just enough Lua and who's in charge between client and server) you can already write things that really work. We're going to build your first script from scratch and, along the way, lock in the concepts you'll repeat for the rest of your life as a FiveM dev.

Everything is a resource

In FiveM you don't write "a loose script": you write a resource. A resource is simply a folder inside resources/ that contains a special file called fxmanifest.lua. That manifest is the index that tells the server which files to load and on which side they run (client, server or both).

text
resources/
└── mi_primer_script/
    ├── fxmanifest.lua
    ├── config.lua
    ├── client.lua
    └── server.lua

Typical structure of a resource

The fxmanifest.lua, piece by piece

If the manifest is wrong, the resource won't even start. Luckily it's short and almost always looks like this. Read it calmly: every line has a reason.

lua
fx_version 'cerulean'   -- manifest version (always use 'cerulean')
game 'gta5'             -- the target game

author 'TuNombre'
description 'Mi primer recurso'
version '1.0.0'

-- Shared: loads on client AND server (ideal for config)
shared_scripts {
  'config.lua'
}

-- Client: runs on each player's PC. NOT trusted.
client_scripts {
  'client.lua'
}

-- Server: runs on your machine. It's the authority.
server_scripts {
  'server.lua'
}

fxmanifest.lua

  • fx_version 'cerulean' and game 'gta5' are mandatory: without them the resource won't load.
  • shared_scripts: load on both sides. Perfect for your config.lua and common data.
  • client_scripts: run on each player's PC. Never trust anything that comes from here.
  • server_scripts: run on your server. They're the source of truth and the one that validates.

To enable your resource add ensure mi_primer_script to server.cfg. To reload it without restarting the server, type in the console: refresh and then ensure mi_primer_script.

Just enough Lua

FiveM uses Lua. You don't need to master it fully: with variables, functions, tables, conditionals and loops you're more than set to begin. And there are two functions you'll see in almost every script: CreateThread (for processes that repeat) and Wait (to avoid burning the CPU).

lua
-- Variables: ALWAYS use local
local nombre = 'Crxative'
local vida = 100

-- Functions
local function curar(cantidad)
  vida = vida + cantidad
  return vida
end

-- Tables (lists and dictionaries)
local armas = { 'pistola', 'rifle' }      -- indices start at 1
local jugador = { nombre = 'Ana', dinero = 500 }

-- Conditionals
if jugador.dinero >= 100 then
  print('puede pagar')
end

-- A thread that repeats every second without freezing the game
CreateThread(function()
  while true do
    Wait(1000)         -- pause 1000 ms (essential!)
    print('sigo vivo')
  end
end)

Essential Lua for FiveM

A while true loop without Wait FREEZES the client or the server. The golden rule: every infinite loop carries at least one Wait inside.

Client vs server: the mental model

This is the most important idea in all of FiveM. The CLIENT runs on each player's computer: it draws the interface, reads keys, shows the world, knows where the player is. But it's manipulable: a cheater can modify it. The SERVER runs on your machine, you control it, and it's the only authority: this is where you keep the money, validate actions and decide what's real.

  • The client ASKS; the server DECIDES.
  • Visual and input logic → client. Money, inventory, permissions, database → server.
  • Sacred rule: never trust data coming from the client. Always validate it on the server.

If you let the client say "give me $1,000,000", they'll give it to themselves. The server must ALWAYS check: can this player really do this?

Network events: how the two sides talk

Client and server don't share variables: they communicate with events. One side fires an event, the other listens for it. The pieces you need to know are few.

  • RegisterNetEvent('name'): registers an event that can come over the network (needed before listening for it).
  • AddEventHandler('name', fn) or the inline handler: defines what to do when the event arrives.
  • TriggerServerEvent('name', ...): the client sends an event to the server.
  • TriggerClientEvent('name', source, ...): the server sends an event to a specific client (source = player id).

In a server event, the global variable source holds the ID of the player who fired it. It's your only reliable source for WHO is acting: use it, don't trust an id the client sends inside the data.

Your first script: the /saludo command

Let's put it all together. The client registers a /saludo command; when used, it notifies the server. The server validates (here, something simple) and replies to the client, which shows a notification on screen. Notice how the flow is always client → server → client.

lua
-- client.lua
RegisterCommand('saludo', function()
  -- The client only ASKS: it tells the server it wants to greet
  TriggerServerEvent('miscript:saludar')
end, false)

-- We listen for the server's reply
RegisterNetEvent('miscript:respuesta')
AddEventHandler('miscript:respuesta', function(mensaje)
  -- Native GTA notification on screen
  BeginTextCommandThefeedPost('STRING')
  AddTextComponentSubstringPlayerName(mensaje)
  EndTextCommandThefeedPostTicker(false, true)
end)

client.lua

lua
-- server.lua
RegisterNetEvent('miscript:saludar')
AddEventHandler('miscript:saludar', function()
  local src = source           -- ID of the player who fired the event

  -- The server VALIDATES: does that player really exist?
  local nombre = GetPlayerName(src)
  if not nombre then return end

  print(('El jugador %s (id %d) ha usado /saludo'):format(nombre, src))

  -- We reply ONLY to that client
  TriggerClientEvent('miscript:respuesta', src, 'Hola, ' .. nombre .. ' 👋')
end)

server.lua

That's all: a real command, with a round trip over the network, where the server is in charge. From here, giving money, opening a menu or saving to a database is exactly the same pattern, just with more validation logic in the middle.

Good practices from day one

  • Move the adjustable values (prices, coordinates, texts) to config.lua. Never hardcode them scattered through the code.
  • Use local on all your variables unless you deliberately need a global.
  • Name your events with a resource prefix ('miscript:saludar') so you don't clash with others.
  • Validate on the server anything that could give a player an advantage (money, items, permissions).
  • Read the console: Lua errors tell you the file and line. They're your best friend.

If you truly understand this chapter, you already know 80% of how any FiveM resource is built. The rest is repeating this pattern with more craft. Create your folder, copy the /saludo command, test it on your server and make it greet you by name: that first "it works" is addictive.

Put it into practice

Got a question about this? The AI chat knows all of it and answers with code.

Ask the AI
How to make scripts in FiveM (Lua, client-server) | Crxative-M