Skip to main content

Introduction

Callbacks in RSG Core provide a way to request data from the server and wait for a response, or vice versa. Theyโ€™re essential for synchronous communication between client and server scripts. Location: resources/[framework]/rsg-core/server/functions.lua & client/functions.lua
Callbacks are perfect for situations where you need to wait for data before continuing execution, like checking if a player has an item, getting database information, or validating actions.

Core Features

๐Ÿ“ก Bidirectional Communication

  • Server Callbacks: Client requests data from server
  • Client Callbacks: Server requests data from specific client
  • Multiple Arguments: Pass any number of arguments
  • Any Data Types: Return tables, strings, numbers, booleans

๐Ÿ”’ Secure by Design

  • Server-side Validation: All sensitive logic stays on server
  • No Client Trust: Client only receives processed results
  • Source Tracking: Always know which player triggered callback

โšก Efficient Data Flow

  • Async Processing: Non-blocking operations
  • Promise Support: Await pattern for cleaner code
  • No Event Spam: Request-response pattern prevents loops

Server Callbacks

Server callbacks allow you to request data from the server and wait for the response on the client.

Creating a Server Callback

Use RSGCore.Functions.CreateCallback on the server to create a callback:
-- SERVER SIDE (server/main.lua or your resource's server file)

RSGCore.Functions.CreateCallback('rsg-example:server:getData', function(source, cb, arg1, arg2)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)

    if not Player then
        cb(nil)
        return
    end

    -- Do your server-side logic
    local data = {
        name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname,
        job = Player.PlayerData.job.label,
        money = Player.PlayerData.money.cash,
        customArg1 = arg1,
        customArg2 = arg2
    }

    -- Send data back to client
    cb(data)
end)
Parameters:
  • source: The player who triggered the callback
  • cb: The callback function to send data back
  • ...: Any additional arguments passed from the client

Triggering a Server Callback

Use RSGCore.Functions.TriggerCallback on the client to call the callback:
-- CLIENT SIDE (client/main.lua or your resource's client file)

RSGCore.Functions.TriggerCallback('rsg-example:server:getData', function(data)
    if data then
        print('Player Name: ' .. data.name)
        print('Job: ' .. data.job)
        print('Cash: $' .. data.money)
    else
        print('No data received')
    end
end, 'argument1', 'argument2')

Client Callbacks

Client callbacks allow the server to request data from a specific client and wait for the response.

Creating a Client Callback

Use RSGCore.Functions.CreateClientCallback on the client to create a callback:
-- CLIENT SIDE

RSGCore.Functions.CreateClientCallback('rsg-example:client:getVehicleData', function(cb)
    local ped = PlayerPedId()
    local vehicle = GetVehiclePedIsIn(ped, false)

    if vehicle ~= 0 then
        local data = {
            model = GetEntityModel(vehicle),
            plate = GetVehicleNumberPlateText(vehicle),
            health = GetVehicleEngineHealth(vehicle),
            coords = GetEntityCoords(vehicle)
        }
        cb(data)
    else
        cb(nil) -- Player not in a vehicle
    end
end)

Triggering a Client Callback

Use RSGCore.Functions.TriggerClientCallback on the server to call the callback:
-- SERVER SIDE

RSGCore.Functions.TriggerClientCallback('rsg-example:client:getVehicleData', source, function(data)
    if data then
        print('Vehicle Model: ' .. data.model)
        print('Plate: ' .. data.plate)
        print('Engine Health: ' .. data.health)
    else
        print('Player is not in a vehicle')
    end
end)

Practical Examples

Example 1: Checking if Player Has Item

Server Callback:
-- SERVER
RSGCore.Functions.CreateCallback('rsg-shop:server:hasEnoughMoney', function(source, cb, amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)

    if not Player then
        cb(false)
        return
    end

    if Player.Functions.GetMoney('cash') >= amount then
        cb(true)
    else
        cb(false)
    end
end)
Client Usage:
-- CLIENT
local price = 50

RSGCore.Functions.TriggerCallback('rsg-shop:server:hasEnoughMoney', function(hasEnough)
    if hasEnough then
        print('You have enough money!')
        -- Proceed with purchase
    else
        print('You don\'t have enough money!')
    end
end, price)

Example 2: Getting Playerโ€™s Horses

Server Callback with Database:
-- SERVER
RSGCore.Functions.CreateCallback('rsg-horses:server:getOwnedHorses', function(source, cb)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)

    if not Player then
        cb({})
        return
    end

    local result = MySQL.query.await('SELECT * FROM player_horses WHERE citizenid = ?', {
        Player.PlayerData.citizenid
    })

    if result and result[1] then
        cb(result)
    else
        cb({})
    end
end)
Client Usage:
-- CLIENT
RSGCore.Functions.TriggerCallback('rsg-horses:server:getOwnedHorses', function(horses)
    if #horses > 0 then
        print('You own ' .. #horses .. ' horses:')
        for i = 1, #horses do
            print('  - ' .. horses[i].name)
        end
    else
        print('You don\'t own any horses')
    end
end)

Example 3: Purchasing Item

Server Callback:
-- SERVER
RSGCore.Functions.CreateCallback('rsg-shop:server:purchaseItem', function(source, cb, itemName, amount, price)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)

    if not Player then
        cb({ success = false, message = 'Player not found' })
        return
    end

    local totalPrice = price * amount

    -- Check if player has enough money
    if Player.Functions.GetMoney('cash') < totalPrice then
        cb({ success = false, message = 'Not enough money' })
        return
    end

    -- Check if player has inventory space
    if not exports['rsg-inventory']:CanAddItem(src, itemName, amount) then
        cb({ success = false, message = 'Not enough inventory space' })
        return
    end

    -- Remove money
    if not Player.Functions.RemoveMoney('cash', totalPrice, 'shop-purchase') then
        cb({ success = false, message = 'Failed to remove money' })
        return
    end

    -- Add item
    if exports['rsg-inventory']:AddItem(src, itemName, amount, nil, nil, 'shop-purchase') then
        cb({ success = true, message = 'Purchase successful' })
    else
        -- Refund money if item add fails
        Player.Functions.AddMoney('cash', totalPrice, 'shop-refund')
        cb({ success = false, message = 'Failed to add item' })
    end
end)
Client Usage:
-- CLIENT
local itemName = 'bread'
local amount = 3
local pricePerUnit = 5

RSGCore.Functions.TriggerCallback('rsg-shop:server:purchaseItem', function(result)
    if result.success then
        lib.notify({ title = 'Shop', description = result.message, type = 'success' })
    else
        lib.notify({ title = 'Shop', description = result.message, type = 'error' })
    end
end, itemName, amount, pricePerUnit)

Example 4: Client Callback for Coordinates

Client Callback:
-- CLIENT
RSGCore.Functions.CreateClientCallback('rsg-admin:client:getPlayerCoords', function(cb)
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local heading = GetEntityHeading(ped)

    cb({
        x = coords.x,
        y = coords.y,
        z = coords.z,
        heading = heading
    })
end)
Server Usage:
-- SERVER
RegisterCommand('getcoords', function(source, args)
    local src = source

    if not RSGCore.Functions.HasPermission(src, 'admin') then return end

    RSGCore.Functions.TriggerClientCallback('rsg-admin:client:getPlayerCoords', src, function(coords)
        if coords then
            print(('Player coords: x=%.2f, y=%.2f, z=%.2f, heading=%.2f'):format(
                coords.x, coords.y, coords.z, coords.heading
            ))
        end
    end)
end)

Best Practices

Use Descriptive Names: Name your callbacks clearly to indicate what they do
  • Good: 'rsg-shop:server:purchaseItem'
  • Bad: 'shop:buy'
Always Validate Data: Never trust client-sent data without validation
-- BAD
RSGCore.Functions.CreateCallback('example', function(source, cb, amount)
    Player.Functions.AddMoney('cash', amount) -- NO VALIDATION!
end)

-- GOOD
RSGCore.Functions.CreateCallback('example', function(source, cb, amount)
    if type(amount) ~= 'number' or amount <= 0 or amount > 1000 then
        cb(false)
        return
    end
    Player.Functions.AddMoney('cash', amount)
    cb(true)
end)
Always Provide Fallbacks: Handle cases where data might be nil
RSGCore.Functions.TriggerCallback('example:getData', function(data)
    if not data then
        print('Failed to get data')
        return
    end
    -- Use data
end)

Performance Tips

  1. Donโ€™t spam callbacks: Avoid calling callbacks in loops or every frame
  2. Cache results: If data doesnโ€™t change often, cache it instead of calling repeatedly
  3. Use events for one-way communication: If you donโ€™t need a response, use events instead
-- BAD: Callback every second
CreateThread(function()
    while true do
        Wait(1000)
        RSGCore.Functions.TriggerCallback('getData', function(data)
            -- Do something
        end)
    end
end)

-- GOOD: Event for one-way or cache for repeated use
local cachedData = nil

RegisterNetEvent('updateData', function(data)
    cachedData = data
end)

-- Request once and cache
RSGCore.Functions.TriggerCallback('getData', function(data)
    cachedData = data
end)

Common Patterns

Pattern 1: Permission Check

-- SERVER
RSGCore.Functions.CreateCallback('checkPermission', function(source, cb, permission)
    cb(RSGCore.Functions.HasPermission(source, permission))
end)

-- CLIENT
RSGCore.Functions.TriggerCallback('checkPermission', function(hasPermission)
    if hasPermission then
        -- Show admin menu
    end
end, 'admin')

Pattern 2: Database Fetch

-- SERVER
RSGCore.Functions.CreateCallback('getPlayerStats', function(source, cb)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then cb(nil) return end

    local stats = MySQL.query.await('SELECT * FROM player_stats WHERE citizenid = ?', {
        Player.PlayerData.citizenid
    })

    cb(stats[1] or {})
end)

Pattern 3: Multi-Step Validation

-- SERVER
RSGCore.Functions.CreateCallback('canCraftItem', function(source, cb, itemName, amount)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then cb({ canCraft = false, reason = 'Invalid player' }) return end

    -- Check recipe exists
    local recipe = Config.Recipes[itemName]
    if not recipe then
        cb({ canCraft = false, reason = 'Unknown recipe' })
        return
    end

    -- Check player has required items
    for _, ingredient in pairs(recipe.required) do
        if not Player.Functions.HasItem(ingredient.item, ingredient.amount * amount) then
            cb({ canCraft = false, reason = 'Missing ' .. ingredient.item })
            return
        end
    end

    -- Check inventory space
    if not exports['rsg-inventory']:CanAddItem(source, itemName, amount) then
        cb({ canCraft = false, reason = 'Inventory full' })
        return
    end

    cb({ canCraft = true })
end)

Troubleshooting

Callback Not Responding

Ensure CreateCallback is called before you try to trigger it:
-- Make sure this runs when resource starts
RSGCore.Functions.CreateCallback('example', function(source, cb)
    cb('data')
end)
Look in your server/client console for Lua errors that might prevent the callback from executing
Make sure the resource containing the callback is actually started:
ensure your-resource

Callback Returns Nil

Common causes:
  • Player object is nil (player left server)
  • Database query returned no results
  • Logic error in callback function
Always handle nil returns:
RSGCore.Functions.TriggerCallback('getData', function(data)
    if not data then
        print('Callback returned nil')
        return
    end
    -- Process data
end)

Advanced: Async/Await Pattern

For cleaner code, you can wrap callbacks in promises:
-- CLIENT
function GetServerDataAsync(arg1, arg2)
    local p = promise.new()

    RSGCore.Functions.TriggerCallback('example:getData', function(data)
        p:resolve(data)
    end, arg1, arg2)

    return Citizen.Await(p)
end

-- Usage
CreateThread(function()
    local data = GetServerDataAsync('test', 123)
    if data then
        print('Got data: ' .. json.encode(data))
    end
end)

Summary

  • Server Callbacks: Client requests data from server
    • CreateCallback on server
    • TriggerCallback on client
  • Client Callbacks: Server requests data from client
    • CreateClientCallback on client
    • TriggerClientCallback on server
  • Always validate data on the server
  • Always handle nil responses
  • Use events for one-way communication
  • Cache data when possible to avoid repeated callbacks

Next Steps


Need help? Join the RSG Framework Discord!