custom/
├── shared/
│ └── custom.lua
├── client/
│ └── notifications.lua
├── server/
│ ├── money.lua
│ └── permissions.lua
└── bridge/
├── banking/server.lua
├── business/server.lua
├── dispatch/server.lua
├── framework/client.lua
├── framework/server.lua
├── fuel/client.lua
├── garage/server.lua
├── housing/server.lua
├── inventory/client.lua
├── inventory/server.lua
├── notify/client.lua
├── uploader/server.lua
├── vehiclekeys/client.lua
├── voice/client.lua
├── voice/server.lua
└── weather/server.lua
Everything in custom/ is listed in escrow_ignore , so it stays open when the resource is encrypted, and it is separate from the core, so phone updates do not overwrite it. The provider files in bridge/ are also open: you can read them or copy one as a starting point.
Every Custom hook returns nil by default, which means "do the default thing". Return anything else and your code takes over.
How a notification looks while the phone is put away. By default the phone peeks up from the bottom of the screen. With the phone open, notifications always show inside it.
Parameter Type Description data table { app, title, content, avatar, thumbnail }
Return Behaviour nil The phone peeks up as usual true You already showed it; the phone does nothing
Use origen_notify instead of the phone:
function Custom . ShowNotification (data)
exports. origen_notify : ShowNotification (data. content , 'info' , 5000 )
return true
end
Only calls and messages use the phone, the rest your notify:
function Custom . ShowNotification (data)
if data. app == 'phone' or data. app == 'messages' then
return nil
end
exports. origen_notify : ShowNotification (data. content , 'info' , 5000 )
return true
end
The hook runs inside pcall . If it errors, the error is printed and the phone shows the notification as usual.
Every charge and payout the phone makes goes through here: ads, taxi fares, bills, transfers, dating subscriptions, line recovery. By default the phone uses the Banking bridge.
Parameter Type Description source number Player server ID amount number Always positive reason string Already translated to the player's language account string|nil Account the player picked, if the app lets them
Return Behaviour nil Charge through the banking bridge true Charged, continue false, 'reason' Failed; the text is shown to the player
Charge cash instead of bank (Qbox):
function Custom . Charge (source, amount, reason)
local player = exports. qbx_core : GetPlayer (source)
if not player then return false , 'Player not found' end
if player. Functions . RemoveMoney ( 'cash' , amount, reason) then
return true
end
return false , 'You do not have enough cash'
end
Free ads for reporters:
function Custom . Charge (source, amount, reason)
local job = Framework. GetJob (source)
if reason : find ( 'Ad' ) and job and job. name == 'reporter' then
return true
end
return nil
end
Pays a player: driver and courier earnings, marketplace sales and refunds. Same return rules as Custom.Charge .
Parameter Type Description source number Player server ID amount number Amount to pay reason string Already translated to the player's language
Your own veto on an app, for conditions that do not fit a panel rule. It runs after the panel rules and can only take access away, never grant it.
Parameter Type Description source number Player server ID appId string 'bank' , 'darkchat' , 'blackmarket' ...
Return Behaviour nil Whatever the panel says false Denied, no matter what
function Custom . CanUseApp (source, appId)
if appId == 'bank' and MyResource. IsJailed (source) then
return false
end
return nil
end
This runs every time a phone opens, once per app (over thirty times in a row). Do not query the database here: keep what you need in memory.
Every category has a template with the exact functions it needs. Fill it in and set the category to 'custom' in config/bridge.lua :
BridgeConfig. Banking = 'custom'
Each template registers itself with Bridge.Register(category, 'custom', { ... }) and is only used when its category is set to 'custom' .
Bridge. Register ( 'Framework' , 'custom' , {
RegisterUsableItem = function (name, cb) end ,
GetPlayer = function (source) return nil end ,
Identifier = function (player) return nil end ,
Name = function (player) return nil end ,
Job = function (player) return nil end ,
Gang = function (player) return nil end ,
})
Method Returns Description RegisterUsableItem(name, cb) Registers the phone item as usable. cb(source, item) GetPlayer(source) table|nil Your framework's player object Identifier(player) string|nil Stable character identifier (phone owner) Name(player) string|nil 'Firstname Lastname' Job(player) table|nil { name, label, type, grade, gradeLabel, onduty } Gang(player) table|nil { name, label, grade, gradeLabel, isBoss }
Bridge. Register ( 'Framework' , 'custom' , {
PlayerLoadedEvent = nil ,
IsDead = function () return false end ,
IsCuffed = function () return false end ,
})
Field Returns Description PlayerLoadedEvent string|nil Client event your framework fires once the character is loaded IsDead() boolean Dead or downed: the phone cannot be used IsCuffed() boolean Cuffed: the phone cannot be used
Method Returns Description Items(source, name) table[]|nil { { slot, metadata } } for that item. nil when the inventory is not loaded yetSetMetadata(source, slot, meta) Writes metadata to the item in that slot All(source) table[] Every item: { name, label, count, slot, metadata } Remove(source, name, count) boolean Removes items Add(target, name, count, meta) boolean Adds items to a player source or a stash id CanCarry(source, name, count) boolean Whether the player can carry the items
Method Returns Description Items() table[]|nil The local player's items, each with name and metadata
Method Returns Description Accounts(citizenid) table[] { { id, name, type, balance } } Charge(source, amount, reason) boolean, string|nil Success, and the error shown to the player Deposit(source, amount, reason) boolean Pays the player Transactions(accountId) table[] { { id, amount (negative = outgoing), description, type, createdAt (ms) } } AddToSociety(account, amount, reason) boolean Deposits a paid bill into the society or business account that issued it
Bridge. Register ( 'Banking' , 'custom' , {
Charge = function (source, amount, reason)
return exports. my_bank : RemoveMoney (source, amount, reason) == true
end ,
-- ...
})
Every method is optional: leave out what your business resource does not support.
Method Returns Description BusinessesOf(citizenid) table[] { { id, name, label, type, onDuty } } businesses this character works forJobList() table[] { { name, label } } business job names, for the admin panelDeliverOrder(businessId, orderId) boolean Delivers a supply order. false when the order no longer exists AddIncome(businessId, amount, kind, source, meta) boolean Adds income to the business till AddExpense(businessId, amount, kind, source, meta) Registers an expense
Method Returns Description SendAlert(alert) boolean alert : { coords (vector3), title, message, job, source, number } . number is nil when anonymousCentralPlayers(job) number[]|nil Sources answering that job's central. nil counts on-duty players instead
Method Returns Description List(citizenid) table[] { { plate, model, garage, state, fuel, engine, body, depotPrice } } . state : 0 out, 1 stored, 2 impounded. engine and body are 0 to 100Find(citizenid, plate) table|nil { plate, model, mods (vehicle properties JSON), label, garage, stored (boolean) } Locate(garage) table|nil { label, coords = { x, y } } TakeOut(plate) boolean Marks the vehicle as out of the garage (valet)
Method Returns Description List(citizenid) table[] { { id, name, mine (boolean), price, kind, keys (count), where = { x, y, z } } } Get(id, citizenid) table|nil Same shape as each List entry HasKey(citizenid, id) boolean Whether the character has a key to that property
Method Returns Description Upload(name, base64) string|nil url, string|nil error Uploads a file. name includes the extension Capture(source, options) string|nil url, string|nil error Captures the player's screen. options : { maxWidth, maxHeight }
Method Returns Description SetCall(source, channel) Puts the player in a call channel. 0 leaves every call GetCall(source) number Current call channel, 0 when in none
Method Returns Description SetCallVolume(volume) Call volume, 5 to 100 RestoreCallEffect(serverId) Restores the call voice effect for that player
Method Returns Description Get() table|nil { weather (GTA weather type, e.g. 'CLEAR'), remaining (ms, optional), blackout, hour, minute }
Bridge. Register ( 'Notify' , 'custom' , {
Send = function (message, kind)
lib. notify ({ description = message, type = kind == 'info' and 'inform' or kind })
end ,
})
Parameter Type Description message string Notification text kind string 'info' , 'success' , 'error' or 'warning'
Bridge. Register ( 'VehicleKeys' , 'custom' , {
GiveKeys = function (vehicle, plate)
end ,
})
Called by the valet when it delivers a vehicle. vehicle is the entity handle.
Bridge. Register ( 'Fuel' , 'custom' , {
SetFuel = function (vehicle, level)
SetVehicleFuelLevel (vehicle, level + 0.0 )
end ,
})
Called by the valet to restore the stored fuel level (0 to 100).