Koja Scripts

Developer API

Koja Spawn Selector API: import.lua, client and server exports, open options, the spawn object, per-player providers, access checks, categories, events and recipes.

Developer API

Other resources can open the selector, add their own locations, lock locations with their own rules and react when a player spawns.

Two ways to call it

Exports work from anywhere:

exports['koja-spawnselector']:Open({ canCancel = true })

import.lua gives you a SpawnSelector table with every export as a function, plus OpenAsync and SpawnAtAsync on the client:

fxmanifest.lua (your resource)
shared_script '@koja-spawnselector/import.lua'
SpawnSelector.Open({ canCancel = true })
local spawn = SpawnSelector.OpenAsync({ canCancel = true })

Client

Open

local opened = exports['koja-spawnselector']:Open(options)

Returns false when the selector is already open, busy, or ClientEditable.CanOpen refused. Every option is optional:

OptionTypeWhat it does
isNewbooleanNew character: list Config.NewCharacter.Spawns instead of the normal list
canCancelbooleanAllow ESC and the exit button. Default Config.Open.CanCancel
categoriesstring[]Only these category ids
lastLocationbooleanfalse hides "Last location"
spawnsspawn[]Extra locations for this opening only (client side, see below)
includeServerbooleanfalse lists only spawns and the client registered ones
selectedstringPreselected spawn id
cinematicbooleanCamera flight on or off for this opening
titlestringReplaces the panel title
onSpawnfunction(spawn)Called after the player landed
onCancelfunction()Called whenever the selector closes without a spawn (ESC, Close, server Close)
Open after your own character creator
exports['koja-spawnselector']:Open({
    isNew = true,
    onSpawn = function(spawn)
        print(('spawned at %s'):format(spawn.label))
    end,
})

OpenAsync (import.lua)

CreateThread(function()
    local spawn = SpawnSelector.OpenAsync({ canCancel = true, categories = { 'property' } })
    if not spawn then return end -- cancelled
    print(spawn.id, spawn.coords.x, spawn.coords.y)
end)

Takes the same options as Open. Returns the spawn, or nil when it was cancelled or could not open.

Close, IsOpen

exports['koja-spawnselector']:Close()  -- calls onCancel
exports['koja-spawnselector']:IsOpen() -- true while open or while the camera flies

RegisterSpawn, RemoveSpawn

A location only this client sees, until you remove it or the resource restarts. Registering the same id again replaces it.

exports['koja-spawnselector']:RegisterSpawn({
    id = 'motel_room',
    label = 'Pink Cage Motel',
    description = 'Room 4',
    category = 'property',
    coords = vec4(312.3, -218.6, 54.2, 160.0),
})
exports['koja-spawnselector']:RemoveSpawn('motel_room')

SpawnAt, Fly, SpawnAtAsync

The same camera flight without the selector — after a jail release, a respawn or your own creator.

exports['koja-spawnselector']:SpawnAt(vec4(1846.4, 2585.9, 45.7, 270.0), {
    label = 'Bolingbroke',
    description = 'Released',
    cinematic = true,
    overlay = true,
    onLanded = function(coords) print('landed') end,
})
exports['koja-spawnselector']:Fly(vec4(-160.0, 6420.0, 31.9, 135.0), { label = 'Paleto Bay' })
OptionWhat it does
label, descriptionText of the letterbox caption. Default: the street name
cinematicCamera flight (true) or a plain fade. Default Config.Cinematic.Enabled
overlayfalse hides the caption
onLandedfunction(coords), called after the player landed

SpawnAt returns true right away (false for invalid coords or while another spawn runs). Fly is SpawnAt with cinematic = true. With import.lua, SpawnSelector.SpawnAtAsync(coords, options) waits until the player landed.

Server

Open, Close, IsSelecting

exports['koja-spawnselector']:Open(source, { canCancel = true })
exports['koja-spawnselector']:Close(source)
exports['koja-spawnselector']:IsSelecting(source)

The options are the client ones without functions (they cannot cross the network). Listen to the events below instead.

RegisterSpawn, RegisterSpawns, RemoveSpawn

Locations for every player, with the same locks as Config.Spawns. They are removed automatically when your resource stops.

exports['koja-spawnselector']:RegisterSpawns({
    { id = 'lsfd', label = 'Davis Fire Station', category = 'job', coords = vec4(204.8, -1641.6, 29.8, 320.0), jobs = { fire = 0 }, onDuty = true },
    { id = 'vu', label = 'Vanilla Unicorn', category = 'job', coords = vec4(129.2, -1299.3, 29.2, 210.0), jobs = 'unicorn' },
})

RegisterSpawn returns true when the definition is valid, RegisterSpawns returns how many were added.

RegisterProvider, RemoveProvider

A provider returns the locations of one player and is called every time that player opens the selector. Use it for houses, apartments, garages, motels — anything a player owns.

exports['koja-spawnselector']:RegisterProvider('my-housing', function(source, identifier)
    local houses = MySQL.query.await('SELECT id, label, x, y, z, h FROM houses WHERE owner = ?', { identifier })
    local list = {}
    for _, house in ipairs(houses or {}) do
        list[#list + 1] = {
            id = 'house:' .. house.id,
            label = house.label,
            category = 'property',
            coords = vec4(house.x, house.y, house.z, house.h),
            data = { house = house.id },
        }
    end
    return list
end)

identifier is the character identifier from koja-lib (ESX identifier, QBCore citizenid). Errors are caught and printed, so one broken provider does not break the screen. Providers are removed when your resource stops.

AddAccessCheck, RemoveAccessCheck

Your own lock rule, run for every listed location after the config locks:

exports['koja-spawnselector']:AddAccessCheck('jail', function(source, spawn)
    if Player(source).state.jailed and spawn.id ~= 'prison' then
        return false, 'In prison'
    end
    return true
end)

Return false, reason to lock the spot; the reason is shown as its label. Anything else allows it. Checks are removed when your resource stops.

RegisterCategory

exports['koja-spawnselector']:RegisterCategory({ id = 'business', label = 'Business', icon = 'building', order = 35 })

order is compared with the order of Config.Categories.

GetSpawns

local list = exports['koja-spawnselector']:GetSpawns(source, { isNew = false })

The exact list this player would see, with locked and lockedLabel filled in.

GetLastLocation, SetLastLocation

local last = exports['koja-spawnselector']:GetLastLocation(source) -- or a character identifier
-- { coords = { x, y, z, w }, updated = 1793532461 } or nil

exports['koja-spawnselector']:SetLastLocation(source, vec4(1846.4, 2585.9, 45.7, 270.0))

Set it before a player logs out somewhere you do not want them back (a heist vault, an event island).

The spawn object

onSpawn, OpenAsync and the events give you:

FieldDescription
id, label, description, category, icon, imageFrom the definition
coords{ x, y, z, w }
dataYour data table
originconfig, server, provider, last or client
updatedLast location only: unix time it was saved
street, zoneClient only: street and zone name at the spot

Events

EventSideArgumentsWhen
koja-spawnselector:client:openedClientoptionsThe map is shown
koja-spawnselector:client:spawnedClientspawn, isNewThe player landed
koja-spawnselector:client:closedClientoptionsClosed without a spawn
koja-spawnselector:server:spawnedServersource, spawn, isNewThe player landed
Server
AddEventHandler('koja-spawnselector:server:spawned', function(source, spawn, isNew)
    if spawn.origin == 'provider' and spawn.data and spawn.data.house then
        TriggerEvent('my-housing:server:enter', source, spawn.data.house)
    end
end)

On the server, spawn is the location the server listed and checked itself — not something the client sent — so its data is safe to act on. The only exception is origin == 'client': a location that came from client side RegisterSpawn / spawns, which the server never saw. Do not trust its data for ownership.

You can also open and close from the server with TriggerClientEvent('koja-spawnselector:client:open', source, options) and TriggerClientEvent('koja-spawnselector:client:close', source).