> Creating Custom FiveM Scripts - A Complete Guide

Learn to create custom FiveM scripts from scratch. Covers Lua basics, client/server scripts, events, and building practical resources for your RP server.

Advanced
2 hours

Creating Custom FiveM Scripts - A Complete Guide

Want to add unique features to your FiveM server? This guide teaches you how to create custom scripts from scratch, covering everything from basic Lua to advanced FiveM concepts.

Understanding FiveM Scripting

FiveM uses Lua for server-side and client-side scripts. Understanding the difference is crucial:

TypeRuns OnPurpose
ClientPlayer's gameUI, player actions, visuals
ServerServer machineDatabase, sync, validation
SharedBothConfig, shared functions
---

Lua Basics for FiveM

Variables & Data Types

Lua
-- Local variables (use these!)
local playerName = "John"
local playerAge = 25
local isOnline = true
local inventory = {"item1", "item2", "item3"}

-- Tables (Lua's arrays/objects)
local playerData = {
    name = "John",
    money = 5000,
    job = "police"
}

-- Accessing table data
print(playerData.name)      -- "John"
print(playerData["money"])  -- 5000

Functions

Lua
-- Basic function
local function greet(name)
    return "Hello, " .. name .. "!"
end

-- Callback pattern
local function getPlayerData(source, callback)
    local data = GetPlayerData(source)
    callback(data)
end

-- Usage
getPlayerData(1, function(data)
    print(data.name)
end)

Control Flow

Lua
-- If/else
if playerData.job == "police" then
    print("Officer on duty")
elseif playerData.job == "medic" then
    print("Medic on duty")
else
    print("Civilian")
end

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

for key, value in pairs(playerData) do
    print(key, value)
end

-- While loop
local count = 0
while count < 5 do
    count = count + 1
end

---

Creating Your First Resource

Resource Structure

my-first-resource/
├── fxmanifest.lua
├── client.lua
├── server.lua
└── config.lua

fxmanifest.lua

Lua
fx_version 'cerulean'
game 'gta5'

author 'Your Name'
description 'My first FiveM resource'
version '1.0.0'

shared_scripts {
    'config.lua'
}

client_scripts {
    'client.lua'
}

server_scripts {
    'server.lua'
}

config.lua (Shared)

Lua
Config = {}

Config.Debug = true
Config.ServerName = "My RP Server"

Config.Locations = {
    police = {
        {x = 425.13, y = -979.56, z = 30.71, name = "Mission Row"}
    },
    hospital = {
        {x = 298.67, y = -584.33, z = 43.26, name = "Pillbox Hill"}
    }
}

Config.NotifyTime = 5000 -- milliseconds

---

Client-Side Scripting

Basic Client Script

Create client.lua:

Lua
local isMenuOpen = false

-- Register a command
RegisterCommand('mymenu', function()
    if isMenuOpen then
        CloseMenu()
    else
        OpenMenu()
    end
end, false)

-- Register key mapping
RegisterKeyMapping('mymenu', 'Open My Menu', 'keyboard', 'F5')

function OpenMenu()
    isMenuOpen = true
    SetNuiFocus(true, true)  -- Enable mouse
    SendNUIMessage({
        action = 'open',
        title = 'My Menu'
    })
end

function CloseMenu()
    isMenuOpen = false
    SetNuiFocus(false, false)  -- Disable mouse
    SendNUIMessage({
        action = 'close'
    })
end

Using Native Functions

FiveM provides access to GTA V native functions:

Lua
-- Get player ped
local playerPed = PlayerPedId()

-- Get player coordinates
local coords = GetEntityCoords(playerPed)
print(coords.x, coords.y, coords.z)

-- Spawn a vehicle
local function SpawnVehicle(model)
    local hash = GetHashKey(model)
    
    -- Request model
    RequestModel(hash)
    while not HasModelLoaded(hash) do
        Wait(100)
    end
    
    -- Get player position
    local playerPed = PlayerPedId()
    local coords = GetEntityCoords(playerPed)
    local heading = GetEntityHeading(playerPed)
    
    -- Create vehicle
    local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, heading, true, false)
    
    -- Put player in vehicle
    SetPedIntoVehicle(playerPed, vehicle, -1)
    
    -- Set as mission entity
    SetEntityAsMissionEntity(vehicle, true, true)
    
    -- Clean up
    SetModelAsNoLongerNeeded(hash)
end

-- Register command
RegisterCommand('car', function(source, args)
    local model = args[1] or 'adder'
    SpawnVehicle(model)
end, false)

Creating Blips

Lua
local function CreateBlip(coords, sprite, color, scale, name)
    local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
    
    SetBlipSprite(blip, sprite)
    SetBlipColour(blip, color)
    SetBlipScale(blip, scale)
    SetBlipAsShortRange(blip, true)
    
    BeginTextCommandSetBlipName("STRING")
    AddTextComponentString(name)
    EndTextCommandSetBlipName(blip)
    
    return blip
end

-- Create a police station blip
CreateThread(function()
    for _, location in pairs(Config.Locations.police) do
        CreateBlip(
            vector3(location.x, location.y, location.z),
            60,   -- sprite
            3,    -- color (blue)
            0.8,  -- scale
            location.name
        )
    end
end)

3D Text Markers

Lua
local function Draw3DText(x, y, z, text)
    local onScreen, _x, _y = World3dToScreen2d(x, y, z)
    
    if onScreen then
        SetTextScale(0.35, 0.35)
        SetTextFont(4)
        SetTextProportional(1)
        SetTextColour(255, 255, 255, 215)
        SetTextEntry("STRING")
        SetTextCentre(1)
        AddTextComponentString(text)
        DrawText(_x, _y)
    end
end

CreateThread(function()
    while true do
        local sleep = 1000
        
        for _, location in pairs(Config.Locations.police) do
            local playerCoords = GetEntityCoords(PlayerPedId())
            local dist = #(playerCoords - vector3(location.x, location.y, location.z))
            
            if dist < 10 then
                sleep = 0
                Draw3DText(location.x, location.y, location.z + 1, "~b~" .. location.name)
                
                if dist < 2 then
                    Draw3DText(location.x, location.y, location.z + 0.5, "~g~Press ~y~E ~g~to enter")
                end
            end
        end
        
        Wait(sleep)
    end
end)

---

Server-Side Scripting

Basic Server Script

Create server.lua:

Lua
-- Player data storage
local Players = {}

-- On player connecting
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local source = source
    local identifier = GetPlayerIdentifierByType(source, 'license')
    
    print('Player connecting: ' .. name .. ' (' .. identifier .. ')')
end)

-- On player spawning
RegisterNetEvent('playerSpawned')
AddEventHandler('playerSpawned', function()
    local source = source
    -- Initialize player data
    Players[source] = {
        money = 1000,
        job = 'unemployed',
        inventory = {}
    }
    
    -- Send data to client
    TriggerClientEvent('updatePlayerData', source, Players[source])
end)

-- Player disconnecting
AddEventHandler('playerDropped', function(reason)
    local source = source
    Players[source] = nil
    print('Player disconnected: ' .. reason)
end)

Server Commands

Lua
-- Client
TriggerServerEvent('myResource:buyItem', itemName, amount)

-- Server
RegisterNetEvent('myResource:buyItem')
AddEventHandler('myResource:buyItem', function(itemName, amount)
    local source = source
    -- Process purchase
end)

Server → Client:

Lua
-- Server
TriggerClientEvent('myResource:notify', source, "Purchase complete!")

-- Client
RegisterNetEvent('myResource:notify')
AddEventHandler('myResource:notify', function(message)
    ShowNotification(message)
end)

Secure Server Callbacks

Lua
-- Server
lib.callback.register('myResource:getPlayerMoney', function(source)
    return Players[source] and Players[source].money or 0
end)

-- Client
local money = lib.callback.await('myResource:getPlayerMoney', false)

---

Creating a Job System

Server-Side Job Handler

Lua
-- config.lua (shared)
Config.Jobs = {
    police = {
        label = "Police Officer",
        salary = 500,
        grades = {
            [0] = {label = "Cadet", salary = 300},
            [1] = {label = "Officer", salary = 500},
            [2] = {label = "Sergeant", salary = 700}
        }
    },
    medic = {
        label = "Medic",
        salary = 400,
        grades = {
            [0] = {label = "Trainee", salary = 250},
            [1] = {label = "Paramedic", salary = 400}
        }
    }
}

Lua
-- server.lua
function SetJob(source, job, grade)
    if not Players[source] then return false end
    if not Config.Jobs[job] then return false end
    
    grade = grade or 0
    
    Players[source].job = {
        name = job,
        grade = grade,
        label = Config.Jobs[job].label,
        gradeLabel = Config.Jobs[job].grades[grade].label
    }
    
    TriggerClientEvent('updatePlayerData', source, Players[source])
    TriggerClientEvent('myResource:jobChanged', source, Players[source].job)
    
    return true
end

RegisterNetEvent('myResource:setJob')
AddEventHandler('myResource:setJob', function(job, grade)
    local source = source
    -- Add permission check here
    SetJob(source, job, grade)
end)

---

Creating UI with NUI

HTML/CSS/JS Interface

Create html/index.html:

HTML
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="menu" class="hidden">
        <h1>My Menu</h1>
        <div class="options">
            <button onclick="selectOption('police')">Police Job</button>
            <button onclick="selectOption('medic')">Medic Job</button>
            <button onclick="selectOption('close')">Close</button>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

Create html/style.css:

CSS
body {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    background: transparent;
}

.hidden { display: none; }

#menu {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: rgba(0, 0, 0, 0.8);
    border-radius: 10px;
    padding: 20px;
    color: white;
    min-width: 250px;
}

button {
    width: 100%;
    padding: 10px;
    margin: 5px 0;
    background: #2196F3;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

button:hover {
    background: #1976D2;
}

Create html/script.js:

JavaScript
// Listen for messages from client
window.addEventListener('message', function(event) {
    const data = event.data;
    
    if (data.action === 'open') {
        document.getElementById('menu').classList.remove('hidden');
    } else if (data.action === 'close') {
        document.getElementById('menu').classList.add('hidden');
    }
});

function selectOption(option) {
    if (option === 'close') {
        fetch(`https://${GetParentResourceName()}/closeMenu`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({})
        });
    } else {
        fetch(`https://${GetParentResourceName()}/selectJob`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ job: option })
        });
    }
}

Update fxmanifest.lua

Lua
ui_page 'html/index.html'

files {
    'html/index.html',
    'html/style.css',
    'html/script.js'
}

Client-Side NUI Handler

Lua
RegisterNUICallback('closeMenu', function(data, cb)
    CloseMenu()
    cb('ok')
end)

RegisterNUICallback('selectJob', function(data, cb)
    TriggerServerEvent('myResource:setJob', data.job, 0)
    CloseMenu()
    cb('ok')
end)

---

Exporting Functions

Make your resource usable by other resources:

Lua
-- server.lua
exports('getPlayerData', function(source)
    return Players[source]
end)

exports('setPlayerMoney', function(source, amount)
    if Players[source] then
        Players[source].money = amount
        TriggerClientEvent('updatePlayerData', source, Players[source])
        return true
    end
    return false
end)

Using Exports from Other Resources

Lua
local money = exports['my-resource']:getPlayerData(source).money
exports['my-resource']:setPlayerMoney(source, 5000)

---

Debugging Tips

Debug Commands

Lua
-- Debug print function
local function DebugPrint(...)
    if Config.Debug then
        print('[DEBUG]', ...)
    end
end

-- Log player position
RegisterCommand('pos', function()
    local coords = GetEntityCoords(PlayerPedId())
    local heading = GetEntityHeading(PlayerPedId())
    print(string.format('Coords: %.2f, %.2f, %.2f, Heading: %.2f', 
        coords.x, coords.y, coords.z, heading))
end, false)

Error Handling

Lua
local function SafeCall(func, ...)
    local success, error = pcall(func, ...)
    if not success then
        print('[ERROR]', error)
        return false
    end
    return true
end

---

Best Practices

  • 1.Always use local variables unless you need global scope
  • 2.Validate all client input on server-side
  • 3.Use event naming convention: resourceName:eventName
  • 4.Clean up resources when players disconnect
  • 5.Use Citizen.Await/Wait instead of infinite loops when possible
  • 6.Document your code with comments
  • 7.Test thoroughly before deployment
  • ---

    Conclusion

    You now have the foundation to create custom FiveM scripts. Start small, test often, and gradually build more complex systems.

    Next steps:

    • >Create a simple job system
    • >Add a phone UI
    • >Build an inventory system
    • >Integrate with a database
    For more information, visit the FiveM Native Reference and FiveM Documentation.

    .. amount .. ' to player ' .. targetId) else TriggerClientEvent('chatMessage', source, 'SERVER', {255, 0, 0}, 'Player not found') end end, false) " onclick="navigator.clipboard.writeText(this.dataset.code).then(()=>{this.querySelector('.copy-icon').style.display='none';this.querySelector('.check-icon').style.display='block';setTimeout(()=>{this.querySelector('.copy-icon').style.display='block';this.querySelector('.check-icon').style.display='none'},2000)})">
    -- Admin command
    RegisterCommand('givemoney', function(source, args, rawCommand)
        -- Check if source is console (source = 0) or has admin permission
        if source == 0 then
            print('Console cannot use this command')
            return
        end
        
        local targetId = tonumber(args[1])
        local amount = tonumber(args[2])
        
        if not targetId or not amount then
            print('Usage: /givemoney [id] [amount]')
            return
        end
        
        -- Add money to target
        if Players[targetId] then
            Players[targetId].money = Players[targetId].money + amount
            TriggerClientEvent('updatePlayerData', targetId, Players[targetId])
            TriggerClientEvent('chatMessage', source, 'SERVER', {0, 255, 0}, 
                'Gave %%CODEBLOCK_11%%#x27; .. amount .. ' to player ' .. targetId)
        else
            TriggerClientEvent('chatMessage', source, 'SERVER', {255, 0, 0}, 
                'Player not found')
        end
    end, false)
    

    ---

    Client-Server Communication

    Triggering Events

    Client → Server: %%CODEBLOCK_12%%

    Server → Client: %%CODEBLOCK_13%%

    Secure Server Callbacks

    %%CODEBLOCK_14%%

    ---

    Creating a Job System

    Server-Side Job Handler

    %%CODEBLOCK_15%%

    %%CODEBLOCK_16%%

    ---

    Creating UI with NUI

    HTML/CSS/JS Interface

    Create html/index.html:

    %%CODEBLOCK_17%%

    Create html/style.css:

    %%CODEBLOCK_18%%

    Create html/script.js:

    %%CODEBLOCK_19%%

    Update fxmanifest.lua

    %%CODEBLOCK_20%%

    Client-Side NUI Handler

    %%CODEBLOCK_21%%

    ---

    Exporting Functions

    Make your resource usable by other resources:

    %%CODEBLOCK_22%%

    Using Exports from Other Resources

    %%CODEBLOCK_23%%

    ---

    Debugging Tips

    Debug Commands

    %%CODEBLOCK_24%%

    Error Handling

    %%CODEBLOCK_25%%

    ---

    Best Practices

  • 1.Always use local variables unless you need global scope
  • 2.Validate all client input on server-side
  • 3.Use event naming convention: resourceName:eventName
  • 4.Clean up resources when players disconnect
  • 5.Use Citizen.Await/Wait instead of infinite loops when possible
  • 6.Document your code with comments
  • 7.Test thoroughly before deployment
  • ---

    Conclusion

    You now have the foundation to create custom FiveM scripts. Start small, test often, and gradually build more complex systems.

    Next steps:

    • >Create a simple job system
    • >Add a phone UI
    • >Build an inventory system
    • >Integrate with a database
    For more information, visit the FiveM Native Reference and FiveM Documentation.