vi/raiser
housing
housing, GTA 6 style illustration
Freebeginnerhousing

Buyable Property + Ownership

Stand on a for-sale marker, press E to buy, and the server checks money, takes payment, and writes ownership to the database.

PRODUCTION-SHAPED, the reference Lua is validated and written framework-correct for ESX, QBCore, and QBox, ready to drop into your server.
Est. Lua
~70 loc
Claude
Claude Opus 4.x / Sonnet 4.x
Validation
syntax-validated
Updated
2026-06-24
Unlock price
$0Free

Included free

Profit potential / morising
$650($175 to $2,400)

$50-389 script band, sized for a rising niche; top sellers clear around $11.85K/mo.

Effort vs return
beginner2-4h

Description

A complete buy flow for a single property: the player walks onto a for-sale marker, sees the price, and presses E to purchase. The server is authoritative, it checks the property is unowned, verifies the player can afford it, removes the money, and inserts the ownership row. This is the transaction core of any player-housing economy.

Prompt Template

You are writing a FiveM script for es_extended (stable, exports-based
getSharedObject) with oxmysql for persistence. Produce ONE Lua file with
"-- ===== client.lua =====" and "-- ===== server.lua =====" banners.

Goal: a buyable property.
- Property: id "[PROP_ID]", label "[PROP_LABEL]", price [PRICE], marker at
  [PROP_COORDS vector3].
- CLIENT: proximity loop (PlayerPedId / GetEntityCoords); within 1.5m show the
  price help text; on IsControlJustReleased(0, 38)
  TriggerServerEvent("housing:buyProperty", PROP_ID). Show the result via a
  "housing:buyResult" handler and ESX.ShowNotification.
- SERVER: on "housing:buyProperty" resolve xPlayer via
  ESX.GetPlayerFromId(source); look up the property in a SERVER-side table.
  Query the DB to confirm it is UNOWNED first, THEN check getMoney(), THEN
  removeMoney(), THEN MySQL.insert the ownership row. Send a success/failure
  back with TriggerClientEvent("housing:buyResult", src, ok, msg).

Requirements: ALL price and money logic on the SERVER (never trust the client);
check ownership in the DB before charging; persist with oxmysql, not a Lua
table; guard xPlayer for nil. Return only the Lua.

Expected Output

The reference Lua at content/expected-outputs/housing/02-property-for-sale.lua shows the client proximity prompt and a server handler that checks DB ownership, validates funds, charges, and inserts the row, all server-authoritative. Split into client.lua / server.lua in fxmanifest.lua.

02-property-for-sale.lua70 lines
-- 02-property-for-sale.lua
-- Buyable property: marker prompt -> server validates money + writes ownership.
-- ESX (exports getSharedObject) + oxmysql for persistence.

-- ===== client.lua =====
local ESX = exports["es_extended"]:getSharedObject()

local PROPERTY = {
    id    = "eclipse_3a",
    label = "Eclipse Towers 3A",
    price = 75000,
    coords = vector3(-773.45, 312.66, 85.70),
}

CreateThread(function()
    while true do
        local sleep = 1000
        local coords = GetEntityCoords(PlayerPedId())
        if #(coords - PROPERTY.coords) < 1.5 then
            sleep = 0
            BeginTextCommandDisplayHelp("STRING")
            AddTextComponentSubstringPlayerName(("Buy ~y~%s~s~ for ~g~$%d~s~ (~INPUT_CONTEXT~)"):format(PROPERTY.label, PROPERTY.price))
            EndTextCommandDisplayHelp(0, false, true, -1)
            if IsControlJustReleased(0, 38) then
                TriggerServerEvent("housing:buyProperty", PROPERTY.id)
            end
        end
        Wait(sleep)
    end
end)

RegisterNetEvent("housing:buyResult", function(ok, msg)
    ESX.ShowNotification(msg)
end)

-- ===== server.lua =====
local ESXs = exports["es_extended"]:getSharedObject()

local PROPERTIES = {
    eclipse_3a = { label = "Eclipse Towers 3A", price = 75000 },
}

RegisterNetEvent("housing:buyProperty", function(propId)
    local src = source
    local prop = PROPERTIES[propId]
    if not prop then return end

    local xPlayer = ESXs.GetPlayerFromId(src)
    if not xPlayer then return end

    -- Already owned by someone? Reject before touching money.
    MySQL.scalar("SELECT owner FROM properties WHERE property_id = ?", { propId }, function(owner)
        if owner then
            TriggerClientEvent("housing:buyResult", src, false, "That property is already owned.")
            return
        end
        if xPlayer.getMoney() < prop.price then
            TriggerClientEvent("housing:buyResult", src, false, "You cannot afford this property.")
            return
        end

        xPlayer.removeMoney(prop.price)
        MySQL.insert(
            "INSERT INTO properties (property_id, owner, label, price) VALUES (?, ?, ?, ?)",
            { propId, xPlayer.identifier, prop.label, prop.price }
        )
        TriggerClientEvent("housing:buyResult", src, true, ("Purchased %s!"):format(prop.label))
    end)
end)

Known Failure Modes

  • Client-side money check, Claude may call removeMoney client-side or trust a client-sent price. Force every money operation onto the server keyed only off PROP_ID.
  • Charge-before-ownership-check, removing money then discovering the property is taken. Order it: confirm unowned → check funds → charge → insert.
  • In-memory ownership, a Lua table loses everything on restart. Require an oxmysql INSERT into a properties table.

Integration Notes

  • Needs a properties table (property_id, owner, label, price). Create it before first run.
  • Declare oxmysql and es_extended as dependencies in fxmanifest.lua and load oxmysql first.
  • Test by buying once (should succeed), then again from a second character (should report "already owned").

Profit Potential

$175-$2400/mo on Tebex (expected ~$650), beginner transaction core priced just below the $50-389 band center against the demand-signal corpus tebex corpus (median ~$11.85K/mo); a ubiquitous need lifts it slightly over a bare instancing script.

Trend Signal

rising, housing systems niche-selection 3.60; corpus ox_doorlock active.

Sales Angle

Position as the server-authoritative purchase core of any housing economy. Recommended Tebex price $59, or bundle it with instancing as a $99 housing-starter that converts new server owners.

Difficulty & Ship Time

beginner · ships in 2-4h.