Browse docs
Client Exports
This page documents the supported public integration calls. Client exports must run in a client script and server exports must run in a server script. Internal NUI callbacks and net events are not public APIs.
sky_phone. Validate every returned success value and keep money,
permissions, ownership, and other authoritative changes on the server.Client Exports
Client exports register and control custom apps owned by the calling resource. Free Phone binds the
owner automatically through GetInvokingResource().
AddCustomApp(definition)
- Purpose: Registers a custom app owned by the calling resource.
- Arguments:
definition(table): Complete app definition.id(string): Stable lowercase app ID. Required.name(string | table): Display name or localized names. Required.ui(string): Localweb/...entry file or allowed remote UI. Required.description(string | table?): Optional description.developer(string?): Developer or resource label.category(string?):games,productivity,shopping,social, orutilities.icon(string?): Localweb/...icon or allowed remote icon.permissions(table?): Requested app permissions.orientation(string?):portrait,landscape, orany.defaultInstalled(boolean?): Installs the app by default.removable(boolean?): Allows the player to uninstall the app.
- Returns:
boolean, string?—trueon success, otherwisefalseand a stable error code.
local app = {
id = "dispatch-board",
name = {
en = "Dispatch Board",
de = "Leitstelle",
},
description = {
en = "Live dispatch information",
de = "Live-Informationen der Leitstelle",
},
developer = "my_dispatch",
category = "productivity",
ui = "web/index.html",
icon = "web/icon.webp",
orientation = "portrait",
defaultInstalled = true,
removable = true,
permissions = {
"app.close",
"device.storage",
"notifications",
"theme.read",
"locale.read",
},
}
local registered, error_code = exports["sky_phone"]:AddCustomApp(app)
if not registered then
print(("[my_dispatch] Registration failed: %s"):format(error_code))
end
Local UI and icon files belong to the calling resource and must be included in that resource's
fxmanifest.lua.
UpdateCustomApp(definition)
- Purpose: Replaces an owned custom app definition.
- Arguments:
definition(table): The complete definition, including the existingid.
- Returns:
boolean, string?.
local updated, error_code = exports["sky_phone"]:UpdateCustomApp({
id = "dispatch-board",
name = "Dispatch Board",
description = "Live incidents and unit status",
developer = "my_dispatch",
category = "productivity",
ui = "web/index.html",
icon = "web/icon.webp",
defaultInstalled = true,
removable = true,
permissions = {
"app.close",
"device.storage",
"notifications",
},
})
if not updated then
print(("[my_dispatch] Update failed: %s"):format(error_code))
end
This export does not accept a partial patch. Omitted fields are removed from the stored definition.
RemoveCustomApp(appId)
- Purpose: Removes an app owned by the calling resource.
- Arguments:
appId(string): App ID used during registration.
- Returns:
boolean, string?.
local removed, error_code = exports["sky_phone"]:RemoveCustomApp("dispatch-board")
if not removed then
print(("[my_dispatch] Removal failed: %s"):format(error_code))
end
Owned apps are also removed automatically when their resource stops.
OpenCustomApp(appId, data?)
- Purpose: Opens an owned app in an already visible phone.
- Arguments:
appId(string): Registered app ID.data(table?): JSON-compatible opening payload delivered to the iframe.
- Returns:
boolean, string?.
RegisterCommand("dispatchphone", function()
local opened, error_code = exports["sky_phone"]:OpenCustomApp(
"dispatch-board",
{
incidentId = 184,
source = "command",
}
)
if not opened then
print(("[my_dispatch] Could not open app: %s"):format(error_code))
end
end)
This export navigates inside an open phone. It does not bypass the phone item, lock screen, or device session.
CloseCustomApp(appId)
- Purpose: Closes the active owned custom app.
- Arguments:
appId(string): Registered app ID.
- Returns:
boolean, string?.
local closed, error_code = exports["sky_phone"]:CloseCustomApp("dispatch-board")
if not closed then
print(("[my_dispatch] Close failed: %s"):format(error_code))
end
SendCustomAppMessage(appId, payload)
- Purpose: Sends JSON-compatible data to an active and ready owned app.
- Arguments:
appId(string): Registered app ID.payload(any): JSON-compatible payload within the configured message-size limit.
- Returns:
boolean, string?.
local sent, error_code = exports["sky_phone"]:SendCustomAppMessage(
"dispatch-board",
{
type = "incident:update",
incident = {
id = 184,
status = "responding",
assignedUnit = "ADAM-12",
},
}
)
if not sent then
print(("[my_dispatch] Message failed: %s"):format(error_code))
end
Messages sent while the app is opening are queued up to the configured queue limit and delivered after the iframe reports bridge readiness.
SendCustomAppNotification(appId, notification)
- Purpose: Creates a validated phone notification for an owned app.
- Arguments:
appId(string): Registered app ID.notification(table): Notification definition.title(string?): Defaults to the app name.text(string): Notification body. Required.subtitle(string?): Optional secondary text.sound(string?):chime,signal, orsoft.critical(boolean?): Requiresnotifications.critical.persistent(boolean?): Keeps the notification visible until handled.route(string?): Must resolve to the app's own route.
- Returns:
boolean, string?.
local sent, error_code = exports["sky_phone"]:SendCustomAppNotification(
"dispatch-board",
{
title = "New dispatch",
subtitle = "Incident #184",
text = "Priority call at Mission Row",
sound = "signal",
}
)
if not sent then
print(("[my_dispatch] Notification failed: %s"):format(error_code))
end
The app definition must contain notifications. Critical notifications additionally require
notifications.critical.
GetCustomAppCapabilities()
- Purpose: Reads the active custom-app ABI, protocol, bridge methods, context capabilities, and configured limits.
- Returns:
table.
local capabilities = exports["sky_phone"]:GetCustomAppCapabilities()
print("ABI", capabilities.abiVersion)
print("Protocol", capabilities.protocolVersion)
print("Enabled", capabilities.enabled)
for _, method in ipairs(capabilities.bridgeMethods or {}) do
print("Bridge method", method)
end
Use capability detection instead of assuming that a future Free Phone version supports a specific bridge extension.