Client-server callback system for request-response communication patterns
The Callbacks component provides a request-response pattern for communication between client and server, allowing one side to request data from the other and receive a response.
Important: The component name is COMPONENTS.Callbacks (plural), not Callback (singular).
source (number) - Player source who triggered callback
data (any) - Data sent from client
cb (function) - Response function to call with results
Example:
Copy
-- Server sideCOMPONENTS.Callbacks:RegisterServerCallback('myresource:getData', function(source, data, cb) local player = Fetch:Source(source) local char = player:GetData('Character') if not char then return cb(false) end local responseData = { name = string.format('%s %s', char:GetData('First'), char:GetData('Last')), cash = char:GetData('Cash'), bank = char:GetData('Bank') } cb(true, responseData)end)
-- Server side - Request data from clientCOMPONENTS.Callbacks:ClientCallback(source, 'myresource:getClientData', { requestType = 'playerInfo'}, function(success, clientData) if success then print('Received from client:', json.encode(clientData)) endend)
-- Client side - Request data from serverCOMPONENTS.Callbacks:ServerCallback('myresource:getData', { type = 'inventory'}, function(success, responseData) if success then print('Player name:', responseData.name) print('Cash:', responseData.cash) else print('Failed to get data') endend)
The extraId parameter prevents callback conflicts when the same event is triggered multiple times simultaneously.Without extraId (can cause conflicts):
Copy
-- If both fire at same time, callbacks may get mixed upCOMPONENTS.Callbacks:ServerCallback('getData', {}, callback1)COMPONENTS.Callbacks:ServerCallback('getData', {}, callback2)
With extraId (safe):
Copy
-- Each callback is uniquely identifiedCOMPONENTS.Callbacks:ServerCallback('getData', {}, callback1, 'request-1')COMPONENTS.Callbacks:ServerCallback('getData', {}, callback2, 'request-2')
When to use:
Multiple simultaneous calls to same callback
Loops that trigger callbacks
Rapid successive calls
Example:
Copy
-- Client side - Multiple requests in loopfor i = 1, 5 do COMPONENTS.Callbacks:ServerCallback('getData', { index = i }, function(success, data) print('Response for index:', i, success) end, tostring(i)) -- extraId prevents mix-upend
-- Register callbackCOMPONENTS.Callbacks:RegisterServerCallback('characters:getCharacterData', function(source, data, cb) local player = Fetch:Source(source) if not player then return cb(false, 'Player not found') end local char = player:GetData('Character') if not char then return cb(false, 'No character selected') end local characterData = { sid = char:GetData('SID'), name = string.format('%s %s', char:GetData('First'), char:GetData('Last')), dob = char:GetData('DOB'), phone = char:GetData('Phone'), job = char:GetData('Job'), cash = char:GetData('Cash'), bank = char:GetData('Bank') } cb(true, characterData)end)
Client:
Copy
-- Request character dataCOMPONENTS.Callbacks:ServerCallback('characters:getCharacterData', {}, function(success, data, error) if success then print('Character:', data.name) print('Money:', data.cash + data.bank) print('Job:', data.job) else print('Error:', error) endend)
-- Register inventory check callbackCOMPONENTS.Callbacks:RegisterServerCallback('inventory:hasItem', function(source, data, cb) local player = Fetch:Source(source) local char = player:GetData('Character') if not char then return cb(false) end local hasItem = COMPONENTS.Inventory:HasItem(char:GetData('SID'), data.itemName, data.count) cb(hasItem, { itemName = data.itemName, required = data.count, has = hasItem })end)
Client:
Copy
-- Check if player has item before actionCOMPONENTS.Callbacks:ServerCallback('inventory:hasItem', { itemName = 'lockpick', count = 1}, function(hasItem, details) if hasItem then -- Start lockpick minigame print('Starting lockpick...') else TriggerEvent('mythic-notifications:client:Send', { message = 'You need a lockpick', type = 'error' }) endend)
-- Register vehicle spawn callbackCOMPONENTS.Callbacks:RegisterServerCallback('garage:spawnVehicle', function(source, data, cb) local player = Fetch:Source(source) local char = player:GetData('Character') if not char then return cb(false, 'Not logged in') end -- Verify ownership COMPONENTS.Vehicles.Owned:GetVIN(data.VIN, function(vehicle) if not vehicle then return cb(false, 'Vehicle not found') end if vehicle.Owner.Type ~= 0 or vehicle.Owner.Id ~= char:GetData('SID') then return cb(false, 'Not your vehicle') end -- Spawn vehicle COMPONENTS.Vehicles.Owned:Spawn( source, data.VIN, data.coords, data.heading, function(success, vehData, entityId) if success then COMPONENTS.Vehicles.Keys:Add(source, data.VIN) cb(true, { entity = entityId, plate = vehData.RegisteredPlate }) else cb(false, 'Spawn failed') end end ) end)end)
Client:
Copy
-- Request vehicle spawnlocal spawnCoords = GetEntityCoords(PlayerPedId())local spawnHeading = GetEntityHeading(PlayerPedId())COMPONENTS.Callbacks:ServerCallback('garage:spawnVehicle', { VIN = selectedVehicle.VIN, coords = spawnCoords, heading = spawnHeading}, function(success, data, error) if success then print('Vehicle spawned:', data.plate) -- Give notification TriggerEvent('mythic-notifications:client:Send', { message = 'Vehicle spawned', type = 'success' }) else print('Failed:', error) TriggerEvent('mythic-notifications:client:Send', { message = error or 'Failed to spawn vehicle', type = 'error' }) endend)
-- Register callback for server to get client infoCOMPONENTS.Callbacks:RegisterClientCallback('getPlayerPosition', function(data, cb) local ped = PlayerPedId() local coords = GetEntityCoords(ped) local heading = GetEntityHeading(ped) local vehicle = GetVehiclePedIsIn(ped, false) cb(true, { coords = coords, heading = heading, inVehicle = vehicle ~= 0, vehicleModel = vehicle ~= 0 and GetEntityModel(vehicle) or nil })end)
Server (Request):
Copy
-- Request position from clientAddEventHandler('admin:getPlayerPosition', function(targetId) local src = source COMPONENTS.Callbacks:ClientCallback(targetId, 'getPlayerPosition', {}, function(success, data) if success then TriggerClientEvent('mythic-notifications:client:Send', src, { message = string.format('Player at: %s', data.coords), type = 'info' }) else TriggerClientEvent('mythic-notifications:client:Send', src, { message = 'Failed to get position', type = 'error' }) end end)end)
-- ✅ Good - handles both success and failureCOMPONENTS.Callbacks:ServerCallback('getData', {}, function(success, data, error) if success then -- Handle success else print('Error:', error) -- Handle failure endend)-- ❌ Bad - assumes successCOMPONENTS.Callbacks:ServerCallback('getData', {}, function(success, data) print('Name:', data.name) -- Crashes if data is nil!end)
Use Descriptive Callback Names
Name callbacks by resource and action:
Copy
-- ✅ Good - clear and organized'inventory:hasItem''characters:getCharacterData''vehicles:spawnVehicle''shop:purchaseItem'-- ❌ Bad - vague names'getData''checkStuff''doThing'
Validate Input Data
Always validate callback data:
Copy
COMPONENTS.Callbacks:RegisterServerCallback('myCallback', function(source, data, cb) -- ✅ Good - validate input if not data or not data.itemName or not data.count then return cb(false, 'Invalid data') end if type(data.count) ~= 'number' or data.count <= 0 then return cb(false, 'Invalid count') end -- Process valid data cb(true, result)end)
Use ExtraId for Loops
Prevent callback conflicts in loops:
Copy
-- ✅ Good - unique extraIdfor i, vehicleVIN in ipairs(vehicles) do COMPONENTS.Callbacks:ServerCallback('vehicles:getData', { VIN = vehicleVIN }, function(success, data) -- Process data end, vehicleVIN) -- Unique extraIdend-- ❌ Bad - callbacks may get mixed upfor i, vehicleVIN in ipairs(vehicles) do COMPONENTS.Callbacks:ServerCallback('vehicles:getData', { VIN = vehicleVIN }, function(success, data) -- Which vehicle is this? end)end
Pro Tip: Create wrapper functions for common callbacks to reduce boilerplate:
Copy
function GetCharacterData(callback) COMPONENTS.Callbacks:ServerCallback('characters:getCharacterData', {}, callback)end-- UsageGetCharacterData(function(success, data) if success then print('Character:', data.name) endend)