Developer API
Other resources can open the selector, add their own locations, lock locations with their own rules and react when a player spawns. Combat logging, death, jail and forced spawns have their own page: Anti combat log & restrictions. The editor, events, photos and stats: Admin tools.
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:
shared_script '@koja-spawnselector/import.lua'SpawnSelector.Open({ canCancel = true })
local spawn = SpawnSelector.OpenAsync({ canCancel = true })
All exports at a glance
| Side | Export | Page |
|---|---|---|
| Client | Open, Close, IsOpen, RegisterSpawn, RemoveSpawn, SpawnAt, Fly | This page |
| Client (import.lua) | SpawnSelector.OpenAsync, SpawnSelector.SpawnAtAsync | This page |
| Server | Open, Close, IsSelecting, RegisterSpawn, RegisterSpawns, RemoveSpawn, RegisterProvider, RemoveProvider, AddAccessCheck, RemoveAccessCheck, RegisterCategory, GetSpawns, GetLastLocation, SetLastLocation | This page |
| Server | MarkCombat, IsInCombat, GetDropRecord, ClearDropRecord, LockLastLocation, UnlockLastLocation, AddLastLocationRule, RemoveLastLocationRule, SetRestriction, ClearRestriction, AddRestrictionHandler, RemoveRestrictionHandler | Anti combat log |
| Server | AddEvent, RemoveEvent, SavePoint, DeletePoint, GetPoints, GetStats | Admin tools |
Everything another resource registers (spawns, providers, checks, rules, handlers, categories) is removed automatically when that resource stops.
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:
| Option | Type | What it does |
|---|---|---|
isNew | boolean | New character: list Config.NewCharacter.Spawns instead of the normal list |
canCancel | boolean | Allow ESC and the exit button. Default Config.Open.CanCancel. Ignored while a restriction is active |
categories | string[] | Only these category ids |
lastLocation | boolean | false hides "Last location" |
spawns | spawn[] | Extra locations for this opening only (client side, see below) |
includeServer | boolean | false lists only spawns and the client registered ones |
selected | string | Preselected spawn id |
cinematic | boolean | Camera flight on or off for this opening |
title | string | Replaces the panel title |
onSpawn | function(spawn) | Called after the player landed |
onCancel | function() | Called whenever the selector closes without a spawn (ESC, Close, server Close) |
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,
vehicle = 'bus',
onLanded = function(coords) print('landed') end,
})
exports['koja-spawnselector']:Fly(vec4(-160.0, 6420.0, 31.9, 135.0), { label = 'Paleto Bay' })
| Option | What it does |
|---|---|
label, description | Text of the letterbox caption. Default: the street name |
cinematic | Camera flight (true) or a plain fade. Default Config.Cinematic.Enabled |
overlay | false hides the caption |
vehicle | Model or { model, plate, props, fuel, keys }: land in the driver seat |
onLanded | function(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.
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. Built-in providers cover the common housing scripts (Config.Housing).
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.
exports['koja-spawnselector']:RegisterProvider('my-garage', function(source, identifier)
local car = MySQL.single.await('SELECT plate, vehicle FROM owned_vehicles WHERE owner = ? AND stored = 1 LIMIT 1', { identifier })
if not car then return {} end
local props = json.decode(car.vehicle)
return { {
id = 'garage:' .. car.plate,
label = 'Legion Square Garage',
description = car.plate,
category = 'property',
coords = vec4(215.9, -809.8, 30.7, 250.0),
vehicle = { model = props.model, plate = car.plate, props = props },
} }
end)AddAccessCheck, RemoveAccessCheck
Your own lock rule, run for every listed location after the config locks:
exports['koja-spawnselector']:AddAccessCheck('vip', function(source, spawn)
if spawn.category == 'vip' and not Player(source).state.vip then
return false, 'VIP only'
end
return true
end)
Return false, reason to lock the spot; the reason is shown as its label. Anything else allows it.
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))
The spawn object
onSpawn, OpenAsync and the events give you:
| Field | Description |
|---|---|
id, label, description, category, icon, blip, image | From the definition |
coords | { x, y, z, w } |
vehicle | { model, plate, props, fuel, keys } when the spot spawns a vehicle |
expiresAt | Events only: unix time the event ends |
data | Your data table |
origin | config, server, provider, last or client |
updated | Last location only: unix time it was saved |
street, zone | Client only: street and zone name at the spot |
Events
| Event | Side | Arguments | When |
|---|---|---|---|
koja-spawnselector:client:opened | Client | options | The map is shown |
koja-spawnselector:client:spawned | Client | spawn, isNew | The player landed |
koja-spawnselector:client:closed | Client | options | Closed without a spawn |
koja-spawnselector:client:combatLogged | Client | payload | A combat logger marker appeared near this player |
koja-spawnselector:server:spawned | Server | source, spawn, isNew | The player landed |
koja-spawnselector:server:combatLog | Server | identifier, record | A player left during combat |
koja-spawnselector:server:pointsChanged | Server | id, point or nil | An editor location was saved or deleted |
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).