> For the complete documentation index, see [llms.txt](https://kazescripts.gitbook.io/kazescripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kazescripts.gitbook.io/kazescripts/kaze_phonepro/custom-apps.md).

# Custom apps

Add your own app to the phone from any resource.

Any resource can add an app to the phone. The app is your own HTML page, shown inside the phone.

## Register the app (client)

```lua
local function register()
    local res = GetCurrentResourceName()
    local ok, err = exports.kaze_phonepro:AddCustomApp({
        identifier = 'my-counter',          -- letters, numbers, - or _
        name = 'Counter',
        ui = LoadResourceFile(res, 'ui/index.html'),   -- or url = 'https://...'
        icon = ('nui://%s/ui/icon.svg'):format(res),
        description = 'A simple counter',
        developer = 'My Server',
        defaultApp = false,                 -- true = on the home screen without installing
        addAppStore = true,                 -- show it in the App Store
        notification = true,
        job = nil,                          -- { police = true } or { police = { 2, 3 } } to limit by job and grade
        onOpen = function() end,
        onClose = function() end,
    })
    if not ok then print(err) end
end

AddEventHandler('kaze_phone:client:loaded', register)
AddEventHandler('onClientResourceStart', function(r) if r == GetCurrentResourceName() then register() end end)
```

Other client exports: `RemoveCustomApp(id)`, `GetCustomApp(id)`, `IsPhoneOpen()`, `SendCustomAppMessage(id, message)`. Apps are removed automatically when your resource stops.

## Talk to Lua from your app

Your page talks to the phone with `window.parent.postMessage`:

```js
const pending = {};
const call = (action, payload = {}, server = true) => new Promise((resolve) => {
  const callbackId = 'cb_' + Date.now() + Math.random();
  pending[callbackId] = resolve;
  window.parent.postMessage({ type: 'kphone:callback', action, payload, callbackId, server }, '*');
});

window.addEventListener('message', (e) => {
  const d = e.data || {};
  if (d.type === 'kphone:callback:response' && pending[d.callbackId]) { pending[d.callbackId](d.result); delete pending[d.callbackId]; }
  if (d.type === 'kphone:init') console.log(d.player.name, d.player.phoneNumber);
  if (d.type === 'broadcast') console.log('message from Lua', d.message);
});
```

| Message type          | Does                                                                    |
| --------------------- | ----------------------------------------------------------------------- |
| `kphone:callback`     | Calls your Lua handler (`server = true` for server, `false` for client) |
| `kphone:notification` | `{ header, message }` notification on the phone                         |
| `kphone:waypoint`     | `{ x, y }` sets a GPS waypoint                                          |
| `kphone:close`        | Closes the phone                                                        |

## Handle callbacks

The event name is `kaze_phone:customApp:<identifier>:<action>`.

```lua
-- server
AddEventHandler('kaze_phone:customApp:my-counter:add', function(src, payload, cb)
    cb({ success = true, count = 1 })
end)

-- client (server = false)
AddEventHandler('kaze_phone:customApp:my-counter:ping', function(payload, cb)
    cb({ success = true })
end)
```

Send a message to an open app from the server with `exports.kaze_phonepro:SendCustomAppMessage(src, id, message)`.

{% hint style="info" %}
A full working example, **kaze\_phone\_example**, is available on our Discord.
{% endhint %}
