Custom housing
Write an adapter so the House app works with an unsupported housing script.
Needs Lua experience. We can't write or debug the adapter for you.
Eight adapters ship built in — loaf_housing, bcs_housing, qs-housing, vms_housing, ps-housing, rtx_housing, qb-houses and esx_property. A ninth is one file: fetch the player's places, map each through HouseEntry, and hand the list back.
The phone never learns your script exists. It only ever sees the shape HouseEntry returns.
1. Set the config
Config.HouseScript = "my_housing"Anything other than auto skips detection, so the name only has to match what you check for in step 2.
2. Create the file
gksphone/client/apps/house/my_housing.luaNo fxmanifest.lua change — client/**/* is already globbed, and the whole folder is outside escrow.
Start it with the guard every adapter uses, so only the selected one loads:
if Config.HouseScript ~= "my_housing" then return end3. Return the houses
fetchHouseData is the only callback that has to do real work. Return one list containing both the places the player owns and the ones they only hold a key to — owned = false marks the difference.
RegisterNUICallback('gksphone:home:fetchHouseData', function(data, cb)
local out = {}
for _, house in pairs(exports['my_housing']:GetMyHouses() or {}) do
out[#out + 1] = HouseEntry({
id = house.id, -- required, unique, stable
label = house.name,
owned = true,
coords = house.coords,
garage = house.has_garage,
price = house.price,
keyholders = HouseKeys(house.keys, function(id, value)
return HouseKey(id, value.name)
end),
-- Only claim what your script can actually do; see below.
canLocate = true,
canGiveKey = true,
canRemoveKey = true,
canTransfer = false,
canLock = false
})
end
Debugprint('my_housing houses', out)
cb(out)
end)HouseEntry fields
Everything is optional except id. What you leave out gets a safe default.
| Field | Type | Default |
|---|---|---|
id | string | number | — required, must be stable |
label | string | the script's name, else #id |
tag | string | small second line on the tile |
owned | boolean | true |
keyholders | list | {} |
garage | boolean | false |
coords | table | vector3 | nil |
tier · price | number | nil |
apartment | boolean | false |
bills · services · upgrades | list | {} — read-only, the phone never pays or buys |
locked | boolean | nil — "unlocked" and "unknown" are different |
The can* flags decide the buttons
canTransfer, canLocate, canGiveKey, canRemoveKey and canLock all default to false, and a tile is only drawn for what is true. Claiming something your script cannot do and then failing is worse than not offering it.
Two helpers
HouseKeys(source, map) walks a keyholder table of any shape — array, map keyed by identifier, map keyed by something else — with pairs, and your mapper decides what key and value mean. Return nil from it to skip an entry.
HouseWaypoint(coords) reads x/y from a table or a vector3 and sets the waypoint. It returns false instead of dropping a marker in the sea when the coordinates are missing.
4. Register the other five
All six callbacks must exist even when the script cannot do the thing — an unregistered callback hangs the app instead of failing. Return false for the ones you skipped.
-- data.id, data.phoneNumber
RegisterNUICallback('gksphone:home:transferHouse', function(data, cb) cb(false) end)
-- data.id, data.coords
RegisterNUICallback('gksphone:home:houseLocation', function(data, cb)
if not HouseWaypoint(data.coords) then
cb(false)
return
end
exports['gksphone']:ToastNotification(
_T(LastItemData?.info?.phoneLang, 'HouseAPP.APP_HOUSE_SETGPS'))
cb('ok')
end)
-- data.id, data.phoneNumber — the recipient is looked up by phone number
RegisterNUICallback('gksphone:home:giveKey', function(data, cb)
CallBackServerTrigger('gksphone:server:getCizitinIDSource', function(_, target)
if not target then return cb(false) end
cb(exports['my_housing']:GiveKey(data.id, tonumber(target)) and 'ok' or false)
end, data.phoneNumber)
end)
-- data.id, data.holderId
RegisterNUICallback('gksphone:home:removeKey', function(data, cb)
if not data.holderId then return cb(false) end
cb(exports['my_housing']:RemoveKey(data.id, data.holderId) and 'ok' or false)
end)
-- data.id, data.locked
RegisterNUICallback('gksphone:home:toggleLock', function(data, cb) cb(false) end)Return 'ok' on success and false on failure. Anything else is treated as failure.
5. Test it
Restart gksphone — a reconnect is not enough — and open the House app. Set Config.Debug = true and Debugprint will show exactly what your adapter handed over.
Stuck on the shape? gksphone/config/house/sh_house.lua is the contract itself, and the eight adapters in client/apps/house/ are worked examples. esx_property.lua is the shortest; loaf_housing.lua shows keys and transfers.

