Table of Contents
1. Introduction to FiveM Scripting
FiveM is a modification framework for Grand Theft Auto V that allows you to create custom multiplayer servers. Unlike traditional GTA V mods, FiveM enables you to build persistent online worlds with custom scripts, vehicles, maps, and gameplay mechanics.
Scripting in FiveM is primarily done using Lua, a lightweight, embeddable scripting language. FiveM scripts can run on both the client (player's computer) and server, creating a distributed architecture that enables complex multiplayer experiences.
💡 Pro Tip: Before diving into scripting, familiarize yourself with the FiveM documentation and understand the difference between client-side and server-side execution.
2. Lua Programming Fundamentals
Variables and Data Types
Lua is dynamically typed, meaning variables don't need type declarations. Here are the basic data types:
-- Variables and data types
local name = "Player" -- String
local age = 25 -- Number
local isAdmin = true -- Boolean
local playerData = { -- Table (object/array)
name = "John Doe",
level = 50,
inventory = {"gun", "ammo"}
}
-- Table access
print(playerData.name) -- "John Doe"
print(playerData["name"]) -- Alternative syntax
print(playerData.inventory[1]) -- "gun"Functions
-- Function definition
local function calculateDamage(baseDamage, armor)
local damage = baseDamage - (armor * 0.5)
return math.max(damage, 0) -- Ensure damage is never negative
end
-- Function call
local finalDamage = calculateDamage(100, 20)
print("Damage dealt:", finalDamage) -- Output: Damage dealt: 90Control Structures
-- If statements
local playerLevel = 45
if playerLevel >= 50 then
print("Player is experienced")
elseif playerLevel >= 25 then
print("Player is intermediate")
else
print("Player is a beginner")
end
-- For loops
for i = 1, 10 do
print("Count:", i)
end
-- While loops
local count = 0
while count < 5 do
count = count + 1
print("Current count:", count)
end3. FiveM Architecture Overview
Understanding FiveM's architecture is crucial for effective script development. FiveM uses a client-server model where scripts can run on both sides.
Client-Side Scripts
- • Run on each player's computer
- • Handle UI, input, and visual effects
- • Access to game natives and player data
- • Limited security - can be modified by players
- • File:
client.lua
Server-Side Scripts
- • Run on the server only
- • Handle game logic, data persistence
- • Database operations and player management
- • Secure - cannot be modified by players
- • File:
server.lua
Communication Between Client and Server
-- Server to Client (TriggerClientEvent)
-- server.lua
TriggerClientEvent('showNotification', playerId, 'Welcome to the server!')
-- client.lua
RegisterNetEvent('showNotification')
AddEventHandler('showNotification', function(message)
-- Show notification to player
SetNotificationTextEntry("STRING")
AddTextComponentString(message)
DrawNotification(false, false)
end)
-- Client to Server (TriggerServerEvent)
-- client.lua
TriggerServerEvent('requestPlayerData', GetPlayerServerId(PlayerId()))
-- server.lua
RegisterNetEvent('requestPlayerData')
AddEventHandler('requestPlayerData', function(playerId)
local playerData = GetPlayerData(playerId)
TriggerClientEvent('receivePlayerData', source, playerData)
end)4. Client-Side Scripting
Client-side scripts handle everything that happens on the player's computer. This includes UI, input handling, visual effects, and local game state.
Basic Client Script Structure
-- client.lua
local isScriptActive = true
-- Main thread - runs continuously
Citizen.CreateThread(function()
while isScriptActive do
-- Your main script logic here
local playerPed = PlayerPedId()
local playerCoords = GetEntityCoords(playerPed)
-- Example: Display coordinates
if IsControlJustPressed(0, 47) then -- G key
print("Player coordinates:", playerCoords.x, playerCoords.y, playerCoords.z)
end
Citizen.Wait(0) -- Important: Always include a wait
end
end)
-- Event handlers
RegisterNetEvent('myScript:showMessage')
AddEventHandler('myScript:showMessage', function(message)
-- Handle received message
print("Received message:", message)
end)
-- Cleanup on resource stop
AddEventHandler('onResourceStop', function(resourceName)
if GetCurrentResourceName() == resourceName then
isScriptActive = false
print("Script stopped")
end
end)Creating Simple UI Elements
-- Simple text display
local function drawText(text, x, y, scale, color)
SetTextFont(4)
SetTextProportional(1)
SetTextScale(scale, scale)
SetTextColour(color.r or 255, color.g or 255, color.b or 255, color.a or 255)
SetTextDropShadow(0, 0, 0, 0, 255)
SetTextEdge(2, 0, 0, 0, 150)
SetTextDropShadow()
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(text)
DrawText(x, y)
end
-- Usage in main thread
Citizen.CreateThread(function()
while true do
drawText("FiveM Script Running", 0.5, 0.1, 0.5, {r = 255, g = 255, b = 255})
Citizen.Wait(0)
end
end)5. Server-Side Scripting
Server-side scripts handle game logic, data persistence, and player management. They're essential for creating persistent multiplayer experiences.
Basic Server Script Structure
-- server.lua
local players = {}
-- Player connection handling
AddEventHandler('playerConnecting', function(playerName, setKickReason, deferrals)
local source = source
print("Player connecting:", playerName, "ID:", source)
-- Add player to tracking
players[source] = {
name = playerName,
connected = true,
joinTime = os.time()
}
end)
-- Player disconnection
AddEventHandler('playerDropped', function(reason)
local source = source
if players[source] then
print("Player disconnected:", players[source].name, "Reason:", reason)
players[source] = nil
end
end)
-- Custom command example
RegisterCommand('kickplayer', function(source, args)
if source == 0 then -- Console command
local targetId = tonumber(args[1])
if targetId and players[targetId] then
DropPlayer(targetId, "Kicked by console")
print("Kicked player:", players[targetId].name)
end
end
end, true) -- true = restricted to consolePlayer Data Management
-- Player data structure
local playerData = {}
-- Load player data (example with MySQL)
function LoadPlayerData(source)
local identifier = GetPlayerIdentifiers(source)[1]
-- Simulate database query
local data = {
money = 5000,
level = 1,
experience = 0,
inventory = {}
}
playerData[source] = data
TriggerClientEvent('playerDataLoaded', source, data)
end
-- Save player data
function SavePlayerData(source)
if playerData[source] then
-- Save to database
print("Saving data for player:", GetPlayerName(source))
-- MySQL.Async.execute('UPDATE players SET money = @money WHERE identifier = @id', {
-- ['@money'] = playerData[source].money,
-- ['@id'] = GetPlayerIdentifiers(source)[1]
-- })
end
end
-- Auto-save every 5 minutes
Citizen.CreateThread(function()
while true do
Citizen.Wait(300000) -- 5 minutes
for playerId, _ in pairs(playerData) do
SavePlayerData(playerId)
end
end
end)6. Best Practices and Optimization
Performance Optimization
❌ Avoid: Never use Citizen.Wait(0) in server scripts. Use Citizen.Wait(1000) or higher for periodic tasks.
✅ Do: Use appropriate wait times. Client UI can use Citizen.Wait(0), but data processing should use longer intervals.
Security Best Practices
- Server-side validation: Always validate data on the server before processing
- Anti-cheat measures: Implement server-side checks for player actions
- Input sanitization: Sanitize all user inputs to prevent exploits
- Rate limiting: Limit the frequency of expensive operations
Code Organization
-- config.lua - Configuration file
Config = {}
Config.Settings = {
MaxPlayers = 32,
StartingMoney = 5000,
EnableDebug = false
}
Config.Commands = {
AdminOnly = true,
Cooldown = 5000 -- milliseconds
}
-- utils.lua - Utility functions
Utils = {}
function Utils.GetPlayerName(source)
return GetPlayerName(source) or "Unknown"
end
function Utils.IsPlayerAdmin(source)
-- Check if player is admin
return IsPlayerAceAllowed(source, "admin")
end7. Advanced Topics
Database Integration
For persistent data, you'll need to integrate with a database. Popular choices include MySQL and MongoDB.
-- Example with oxmysql
local function CreatePlayer(identifier, data)
MySQL.Async.execute('INSERT INTO players (identifier, money, level) VALUES (@id, @money, @level)', {
['@id'] = identifier,
['@money'] = data.money or 5000,
['@level'] = data.level or 1
})
end
local function GetPlayerData(identifier)
local result = MySQL.Sync.fetchAll('SELECT * FROM players WHERE identifier = @id', {
['@id'] = identifier
})
return result[1]
endCustom UI with HTML/CSS/JS
For complex user interfaces, you can create custom HTML pages with CSS and JavaScript.
-- client.lua
local isUIOpen = false
-- Open custom UI
function OpenCustomUI()
SetNuiFocus(true, true)
SendNUIMessage({
type = "openUI",
data = {
playerName = GetPlayerName(PlayerId()),
playerMoney = 5000
}
})
isUIOpen = true
end
-- Handle NUI callbacks
RegisterNUICallback('closeUI', function(data, cb)
SetNuiFocus(false, false)
isUIOpen = false
cb('ok')
end)
RegisterNUICallback('updateMoney', function(data, cb)
-- Handle money update from UI
TriggerServerEvent('updatePlayerMoney', data.amount)
cb('ok')
end)8. Conclusion
FiveM script development is a powerful way to create custom multiplayer experiences in GTA V. By understanding Lua programming, FiveM's architecture, and following best practices, you can create engaging scripts that enhance the gameplay for your server's community.
Next Steps
- • Practice with simple scripts before tackling complex systems
- • Join the FiveM Community for support and resources
- • Study existing scripts to understand different approaches
- • Test your scripts thoroughly before deploying to production
- • Keep learning and stay updated with FiveM updates
Ready to Start Scripting?
Use our AI-powered script generator to create professional FiveM scripts in seconds, or browse our collection of free templates to get started quickly.