Agency Docs
QBESXStandalone

Agency-Phone Custom Apps

Now with AgencyOS27: a free update for everyone who already owns the phone.

v27.1.2Paid8 Pages

🧩 Build a custom app for Agency-Phone

There are two ways to put an app on the phone. Pick the one that fits you:

MethodBest forWhat you touch
A. SDK (external resource)Vendors and scripts adding an app with their own iconOnly your own resource. Zero edits to agency-phone.
B. Drop-in (inside agency-phone)Server owners adding a native screen inside the phone UIconfig.lua + apps/ (+ html/js for a screen)

If you ship an app to other servers, use Method A. It needs no source from us and keeps working across updates.

A SDK: register an app from your own resource

agency-phone exposes exports that let any resource put an app with its own icon on the phone home screen at runtime. Your app's interface lives in your own resource; the phone shows the icon and hands control to you when the player taps it. No edits to agency-phone, no unlocked build.

Minimal example

-- client side of YOUR resource
CreateThread(function()
    Wait(1500) -- let agency-phone boot
    exports['agency-phone']:RegisterApp({
        id        = 'myshop',
        label     = 'My Shop',
        icon      = 'fa-solid fa-cart-shopping', -- FontAwesome 6 class
        -- image  = 'https://cfx-nui-myresource/icon.png', -- or your own image instead
        color     = '#ff9500',
        colorDark = '#e08600',
    })
end)

-- fired when the player taps your icon on the phone
AddEventHandler('agency-phone:client:openApp', function(appId)
    if appId ~= 'myshop' then return end
    exports['agency-phone']:ClosePhone()
    -- open your own NUI here
end)

RegisterApp config

FieldTypeRequiredDescription
idstringyesUnique id, lowercase, no spaces.
labelstringyesName shown under the icon.
iconstringnoFontAwesome 6 class. Ignored if image is set.
imagestringnoCustom icon image, e.g. https://cfx-nui-<resource>/icon.png.
color / colorDarkstringnoGradient behind the icon (hex).
slotnumbernoHome screen position. Placed automatically if omitted.
storeDescstringnoShort description on your app's Store page. Falls back to the label.

SDK exports (client)

ExportDescription
RegisterApp(config)Add or update your app. Returns true, or false, err.
UnregisterApp(id)Remove your app. Also happens automatically when your resource stops.
GetRegisteredApps()Table of all registered external apps.
ClosePhone()Close the phone, e.g. before opening your own NUI.

The tap event

When the player taps your icon, agency-phone fires both of these on the client. Use whichever you prefer:

EventArgs
agency-phone:client:openAppappId (filter by your id)
agency-phone:client:openApp:<yourId>none (already scoped to your app)

Add dependency 'agency-phone' to your fxmanifest.lua so the start order is correct.

B Drop-in, a native screen inside the phone

For a screen rendered by the phone itself (not your resource). This edits agency-phone's own files.

StepFileEditable in escrow build?
1. App entry in AgencyConfig.Appsconfig.lua✓ yes
2. RegisterNUICallback logicapps/<id>_client.lua (+ _server.lua)✓ yes
3. Screen <div id="app-<id>">html/index.html✗ encrypted
4. case '<id>': in openApp() + loaderhtml/js/phone.js✗ encrypted

Steps 3 and 4 live in escrow-encrypted files, so a native screen needs a source / unlocked build. If you only have the escrow version, use Method A instead (your screen lives in your own resource). Reference files inside the resource: apps/testapp_client.lua and apps/dispatch_client.lua.

📋 Other phone exports

Client

ExportReturnsDescription
IsPhoneOpen()booleanPhone currently open.
IsPhonePoweredOn()booleanDevice powered on.
HasPhone()booleanPlayer owns a phone item.
HasCarrierContract()booleanActive eSIM / contract.
GetPhoneData()tableSnapshot of phone state.
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon)voidIn-phone notification.
StartCall(contact)voidCall { name, number }.
StartAgencyPayCheckout(opts)voidAgencyPay checkout (see below).

Server

ExportDescription
SendMail(identifier, mailData)Deliver mail to a player (online or offline).
AddChirp(data)Post a message to the Pulse social feed.
VerifyAgencyPayCharge(source, requestId, minAmount)Confirm a paid AgencyPay checkout on the server before you hand out goods (see below).

⇄ NUI ↔ Lua bridge (Method B)

DirectionJS sideLua side
JS → LuafetchNUI('callbackName', data)RegisterNUICallback('callbackName', fn)
Lua → JSlisten for action in window.onmessageSendNUIMessage({ action = '...' })

💳 AgencyPay checkout

Charge the player's wallet from any resource.

exports['agency-phone']:StartAgencyPayCheckout({
    merchant = 'My Shop', title = 'Agency Pay', subtitle = 'Checkout',
    label = 'Premium Coffee', amount = 24.50,
    icon = 'fa-solid fa-mug-hot', accent = '#ff9f0a',
    clientEvent = 'myshop:client:agencyPayResult',
    serverEvent = 'myshop:server:agencyPayResult',
    metadata = { orderId = 'order_2048' },
})

RegisterNetEvent('myshop:client:agencyPayResult', function(result)
    if result.status == 'paid' then
        -- deliver the goods
    end
end)

The result payload includes status (paid / cancelled / failed), amount, cardId, balance and your metadata.

A paid result that reaches your resource from the client proves nothing on its own. Pass your own id to StartAgencyPayCheckout and call exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount) on the server before you hand anything out. It returns true and the amount only if this player paid that checkout in the last 60 seconds, and it uses the charge up, so the same payment cannot be claimed twice.

🐛 Troubleshooting

SymptomFix
Icon doesn't appear (SDK)Update agency-phone to the latest Keymaster build, Wait(1500) before RegisterApp, and add dependency 'agency-phone' to your manifest.
Tapping does nothing (SDK)Your agency-phone:client:openApp handler must match your id exactly (case-sensitive).
Custom image not showingUse a full NUI URL https://cfx-nui-<your-resource>/path/icon.png and list the file in files { } in your manifest.
Icon doesn't appear (drop-in)enabled = true and installed = true in the config entry.

Questions, or need a hand? Join the Agency Scripts Discord.

Still stuck after this page? Our support team takes it from here.