GKSHOP
GKSPHONE V2

Custom App

Build your own app inside GKSPHONE — registration, lifecycle, and the window.gksphone bridge.

A custom app is your own FiveM resource with a NUI page, registered with the phone and rendered inside it. Start from the template on GitHub.

Register the app

Call AddCustomApp client-side, once, after the phone has started:

exports["gksphone"]:AddCustomApp({
    name        = "mdt",                                        -- required, unique
    appurl      = "https://cfx-nui-gksphone-app/ui/index.html", -- required
    icons       = "/html/img/icons/mdt.png",
    description = "Police records",
    show        = true,
    startapp    = false,
    blockedjobs = {},
    allowjob    = {},
    labelLangs  = { en = "MDT", tr = "MDT", de = "MDT" },
})
Field
nameRequired. Unique. Registering the same name again replaces the previous entry
appurlRequired. https://cfx-nui-<your-resource>/<path>
iconsIcon path
descriptionLine shown in the App Gallery
showListed in the App Gallery. Defaults to true
startappOn the home screen at setup. Defaults to false
blockedjobs / allowjobJob restrictions — see Apps
labelLangsDisplay name per language code
onOpen / onCloseCallbacks fired when the player opens or closes the app — see below

url is set to /customapp for you — anything you pass is overwritten.

The app closes automatically if your resource stops.

Lifecycle

Run something when the player opens or closes your app. Declare the callbacks inline:

exports["gksphone"]:AddCustomApp({
    name    = "mdt",
    appurl  = "https://cfx-nui-gksphone-app/ui/index.html",
    onOpen  = function(phoneId, phoneNumber) print("opened by", phoneNumber) end,
    onClose = function(phoneId, phoneNumber) end,
})

Or register them separately, which also lets you replace them later:

exports["gksphone"]:onAppOpen("gksphone-app", function(phoneId, phoneNumber) end)
exports["gksphone"]:onAppClose("gksphone-app", function(phoneId, phoneNumber) end)
exports["gksphone"]:removeAppCallbacks("gksphone-app")

These three take your resource name, not the app's name. The phone derives it from appurlhttps://cfx-nui-gksphone-app/… gives gksphone-app. Pass the display name and the callback never fires.

Both callbacks receive the phone's unique id and its number.

The window.gksphone bridge

The phone injects window.gksphone into your iframe. It's ready once the page has loaded:

document.addEventListener('DOMContentLoaded', () => {
    setTimeout(() => {
        if (window.gksphone) console.log('bridge ready')
    }, 100)
})
FunctionReturns
isDarkMode()true when the phone is in dark mode
onChangeDarkMode(cb)Registers a callback fired when the player switches theme
notify(text, timeout)— Toast. timeout defaults to 2000 ms
loadingPopup(text) / closeLoadingPopup()— Blocking spinner
calling(number, anon)— Starts a voice call
videoCall(number)— Starts a VidMeet call
CameraOpen(photo, video)async — media URL, or null if cancelled
GetGallery(onlyVideo, onlyPicture, multiple, camera)async{ data, opencamera }, or null
FullScreenImage(url)— Opens the image viewer
SelectEmoji(true)— Opens the emoji picker
setStatusBarColor(color)— Sets the app's background behind the status bar
fetchNui(event, data)— Sends an NUI message to the phone
createMap(container, options)Map controller — see below
urlYour resource name, taken from appurl

Both are async — await them:

const photo = await window.gksphone.CameraOpen(true, false)   // photo on, video off
if (photo) console.log(photo)

const picked = await window.gksphone.GetGallery(false, true, false, true)
// { data: "<url>", opencamera: false }  — a file was picked
// { data: false,   opencamera: true  }  — the player tapped the camera icon instead

Emoji picker

SelectEmoji takes a boolean, not a URL. The selection arrives as a message:

window.gksphone.SelectEmoji(true)

window.addEventListener('message', (event) => {
    if (event.data?.type === 'emojiSelected') {
        console.log(event.data.eventData)
    }
})

Game map

const map = gksphone.createMap(document.getElementById('map'), {
  zoom: 3,
  center: { x: 428.9, y: -984.5 },
  map: 'losSantos',    // | 'cayoPerico'
  style: 'atlas',      // | 'satellite' | 'grid'
  allowMoving: false
})
await map.ready

map.setZoom(4)
map.setPosition({ x: 100, y: 200 }, 5)
map.setMap('cayoPerico')
map.setStyle('satellite')
await map.setShowSelf(true)              // live player pin, polled ~3s

const pin = map.addLocation({ title: 'LSPD', coords: { x: 428.9, y: -984.5 } })
map.removeLocation(pin)

map.destroy()                            // call this when leaving the view

Player position and waypoints go through fetchNui:

gksphone.fetchNui('gksphone:get:location')
gksphone.fetchNui('set:gps', { x, y })

Lua-side helpers

NuiSendMessage

Use this instead of SendNUIMessage — it routes the message into the phone's iframe:

exports["gksphone"]:NuiSendMessage({ event = 'showMessage', hello = "world" })
window.addEventListener('message', (event) => {
    if (event.data?.event === 'showMessage') {
        console.log(`Hello ${event.data.hello}!`)
    }
})

InputChange

Stops the player walking while they type in a field:

exports["gksphone"]:InputChange(true)   -- true while focused, false on blur
<input type="text" onfocus="inputFocused(true)" onblur="inputFocused(false)">

On this page