Callbacks Guide
Learn how to use RSG Core's callback system for client-server communication
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 callbackcb: 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
Tip
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
- Don't spam callbacks: Avoid calling callbacks in loops or every frame
- Cache results: If data doesn't change often, cache it instead of calling repeatedly
- 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-resourceCallback 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
CreateCallbackon serverTriggerCallbackon client
-
Client Callbacks: Server requests data from client
CreateClientCallbackon clientTriggerClientCallbackon 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
- Server Events Guide - One-way server-client communication
- Player Data - Access and modify player data
- Server Functions Reference - Complete function list
- Inventory Functions - Work with inventory items
Need help? Join the RSG Framework Discord!

