Property System Architecture
A real estate system transforms your FiveM server from a collection of inaccessible buildings into a living property market where players buy, sell, and rent homes, apartments, and commercial spaces. The architecture centers around a property registry database that tracks every available property on the server with its location, type, current owner, market value, and interior configuration. Unlike simple housing scripts that only let players buy a predetermined list of properties, a well-designed real estate system includes a real estate agent job that creates player-driven transactions with showings, negotiations, and commission structures. The system needs to integrate with your server's door lock resource so that property ownership automatically grants or revokes access to the physical building, and with interior loading systems like custom MLOs or instanced interiors. Plan for three property categories from the start: residential properties for player housing, commercial properties for player-owned businesses, and rental properties that generate passive income for landlords.
Property Database and Listings
The property database is the foundation that every other component builds upon. Each property record needs extensive metadata including the shell or MLO interior it uses, the entry and exit coordinates, garage availability, storage capacity, and pricing history. Maintain a separate listings table that tracks which properties are currently on the market, their asking price, the listing agent, and how long they have been listed. This separation allows you to track market history and calculate property value trends over time. Here is a database schema that supports the full real estate lifecycle:
CREATE TABLE IF NOT EXISTS properties (
id INT AUTO_INCREMENT PRIMARY KEY,
property_id VARCHAR(50) NOT NULL UNIQUE,
label VARCHAR(100) NOT NULL,
property_type ENUM('house', 'apartment', 'commercial', 'penthouse') NOT NULL,
owner VARCHAR(50) DEFAULT NULL,
price INT NOT NULL,
original_price INT NOT NULL,
interior VARCHAR(50) DEFAULT 'standard_1bed',
enter_coords JSON NOT NULL,
exit_coords JSON DEFAULT NULL,
garage_slots INT DEFAULT 0,
stash_slots INT DEFAULT 50,
has_wardrobe TINYINT DEFAULT 1,
is_furnished TINYINT DEFAULT 1,
neighborhood VARCHAR(50) DEFAULT 'downtown',
purchased_at BIGINT DEFAULT NULL,
INDEX idx_owner (owner),
INDEX idx_type (property_type),
INDEX idx_neighborhood (neighborhood)
);
CREATE TABLE IF NOT EXISTS property_listings (
id INT AUTO_INCREMENT PRIMARY KEY,
property_id VARCHAR(50) NOT NULL,
asking_price INT NOT NULL,
listing_agent VARCHAR(50) DEFAULT NULL,
listed_at BIGINT NOT NULL,
status ENUM('active', 'pending', 'sold', 'cancelled') DEFAULT 'active',
buyer VARCHAR(50) DEFAULT NULL,
sold_at BIGINT DEFAULT NULL,
sold_price INT DEFAULT NULL,
INDEX idx_status (status),
INDEX idx_agent (listing_agent)
);
Populate the properties table with all available properties on your server during initial setup. Each property should have carefully set coordinates for entry points that align with door positions or interaction zones. The original_price field preserves the base value for market calculations while the price field reflects current market value that changes based on neighborhood demand, improvements made by the owner, and overall server economic conditions.
Real Estate Agent Job System
The real estate agent job is where the human element makes this system shine. Agents serve as intermediaries between buyers and sellers, conducting property showings, negotiating prices, and earning commission on completed sales. Define job grades that unlock progressively better listings: junior agents handle apartments and small houses, senior agents access luxury properties and commercial spaces, and the agency owner manages the team and takes a cut of all commissions. When a player interested in buying a property contacts an agent, the agent can initiate a showing that teleports both players to the property interior for a walkthrough. Here is the showing and sale transaction logic:
RegisterNetEvent('realestate:server:startShowing', function(propertyId, buyerId)
local src = source
local Agent = QBCore.Functions.GetPlayer(src)
local Buyer = QBCore.Functions.GetPlayer(buyerId)
if not Agent or not Buyer then return end
if Agent.PlayerData.job.name ~= 'realtor' then return end
local property = MySQL.query.await(
'SELECT * FROM properties WHERE property_id = ? AND owner IS NULL',
{propertyId}
)
if not property or not property[1] then
return TriggerClientEvent('QBCore:Notify', src, 'Property not available', 'error')
end
local prop = property[1]
local enterCoords = json.decode(prop.enter_coords)
-- Teleport both to property
TriggerClientEvent('realestate:client:enterShowing', src, prop, enterCoords)
TriggerClientEvent('realestate:client:enterShowing', buyerId, prop, enterCoords)
TriggerClientEvent('QBCore:Notify', buyerId,
'Showing: ' .. prop.label .. ' - $' .. prop.price, 'info')
end)
RegisterNetEvent('realestate:server:purchaseProperty', function(propertyId, buyerId, agreedPrice)
local src = source
local Agent = QBCore.Functions.GetPlayer(src)
local Buyer = QBCore.Functions.GetPlayer(buyerId)
if not Agent or not Buyer then return end
if Agent.PlayerData.job.name ~= 'realtor' then return end
local property = MySQL.query.await(
'SELECT * FROM properties WHERE property_id = ? AND owner IS NULL',
{propertyId}
)
if not property or not property[1] then return end
if not Buyer.Functions.RemoveMoney('bank', agreedPrice, 'property-purchase') then
return TriggerClientEvent('QBCore:Notify', src, 'Buyer cannot afford this', 'error')
end
local commission = math.floor(agreedPrice * 0.05)
Agent.Functions.AddMoney('bank', commission, 'realtor-commission')
MySQL.update(
'UPDATE properties SET owner = ?, price = ?, purchased_at = ? WHERE property_id = ?',
{Buyer.PlayerData.citizenid, agreedPrice, os.time(), propertyId}
)
MySQL.update(
'UPDATE property_listings SET status = "sold", buyer = ?, sold_at = ?, sold_price = ? WHERE property_id = ? AND status = "active"',
{Buyer.PlayerData.citizenid, os.time(), agreedPrice, propertyId}
)
TriggerClientEvent('QBCore:Notify', src,
'Sale complete! Commission: $' .. commission, 'success')
TriggerClientEvent('QBCore:Notify', buyerId,
'Congratulations! You now own ' .. property[1].label, 'success')
TriggerEvent('doorlock:server:updateProperty', propertyId, Buyer.PlayerData.citizenid)
end)
The commission rate should be configurable and potentially variable based on the agent's grade level. Senior agents might earn 7% commission while juniors earn only 3%, motivating career progression within the real estate job. Track each agent's total sales volume and commission earnings in the database to create performance leaderboards and unlock bonuses for top performers.
Mortgage and Financing System
Most players cannot afford high-value properties outright, so a mortgage system dramatically increases the pool of potential buyers and makes the property market accessible to newer players. When a buyer cannot pay the full price, offer a mortgage option that requires a configurable down payment percentage, typically 10-20% of the property value. The remaining balance is paid in regular installments deducted automatically from the player's bank account on each server restart or at scheduled intervals. Track mortgage details including the principal amount, interest rate, remaining balance, monthly payment amount, and payment history. If a player misses too many consecutive payments, trigger a foreclosure process that repossesses the property and relists it on the market at a discount. The interest rate creates a real cost to borrowing that encourages players to pay off their mortgage early when possible, and the system generates a steady drain on the money supply that helps combat inflation on the server economy.
Rental Income and Landlord System
Players who own multiple properties should be able to rent them out for passive income, creating a landlord-tenant dynamic that generates ongoing roleplay. When a property owner wants to rent their property, they set a monthly rent amount and list it as available for rent through the real estate agency or a direct listing. Tenants gain key access to the property and can use its storage, wardrobe, and garage for the duration of their lease. Rent is collected automatically on a configurable schedule and deposited into the landlord's bank account. Here is the rental system implementation:
Config.RentalSettings = {
minRentPrice = 100,
maxRentPrice = 50000,
collectionInterval = 86400, -- every 24 hours real time
maxMissedPayments = 3,
depositMultiplier = 2, -- security deposit = 2x monthly rent
}
RegisterNetEvent('realestate:server:listForRent', function(propertyId, rentPrice)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local property = MySQL.query.await(
'SELECT * FROM properties WHERE property_id = ? AND owner = ?',
{propertyId, Player.PlayerData.citizenid}
)
if not property or not property[1] then return end
if rentPrice < Config.RentalSettings.minRentPrice or
rentPrice > Config.RentalSettings.maxRentPrice then
return TriggerClientEvent('QBCore:Notify', src, 'Invalid rent price', 'error')
end
MySQL.insert(
'INSERT INTO property_rentals (property_id, landlord, rent_price, deposit, listed_at) VALUES (?, ?, ?, ?, ?)',
{propertyId, Player.PlayerData.citizenid, rentPrice,
rentPrice * Config.RentalSettings.depositMultiplier, os.time()}
)
TriggerClientEvent('QBCore:Notify', src,
'Property listed for rent at $' .. rentPrice .. '/day', 'success')
end)
-- Automated rent collection thread
CreateThread(function()
while true do
Wait(1000 * 60 * 60) -- Check every hour
local rentals = MySQL.query.await(
'SELECT * FROM property_rentals WHERE tenant IS NOT NULL AND status = "active"'
)
for _, rental in ipairs(rentals or {}) do
if os.time() - rental.last_collected >= Config.RentalSettings.collectionInterval then
local success = ProcessRentPayment(rental)
if not success then
HandleMissedPayment(rental)
end
end
end
end
end)
The security deposit protects landlords from tenants who damage the property or leave without notice. When a tenant ends their lease, the deposit is returned minus any deductions for damages. Implement a property condition system that tracks wear and tear, giving landlords a legitimate reason to withhold part of the deposit if the tenant was not careful with the property. This creates natural conflict resolution scenarios that drive roleplay between landlords and tenants.
Dynamic Market Pricing
A static property market quickly becomes stale once every desirable property is purchased. Dynamic pricing keeps the market interesting by adjusting property values based on real activity on the server. Track several factors that influence neighborhood value: the number of recent sales in an area, the proximity to popular locations like job centers and shops, crime statistics from the police system, and the overall ratio of occupied to vacant properties. When a neighborhood has high demand with many recent purchases, property values increase gradually, creating a speculative element where early buyers in emerging areas profit as the neighborhood develops. Conversely, high-crime areas or neighborhoods with many vacant properties see declining values. Run the market adjustment as a scheduled server task that recalculates all property values weekly, applying changes gradually to avoid sudden market shocks. Display market trends in the real estate agency's NUI panel so agents and buyers can make informed decisions about where to invest.
Property Showings and NUI Interface
The NUI interface is where agents and buyers browse available properties, review details, and initiate transactions. Build a clean, professional-looking property listing page that displays each available property with its photo or interior preview, location on the map, price, number of rooms, garage capacity, and neighborhood rating. Include filtering and sorting options so users can narrow results by property type, price range, and location. When an agent selects a property for showing, the system should display a route to the property entrance and notify the buyer to follow. Inside the property, provide an interactive tour mode where the agent can highlight features using 3D markers or text annotations. After the showing, present the buyer with a purchase or rental confirmation panel that shows the full financial breakdown including price, taxes, agent commission, and mortgage terms if applicable. The interface should also include a transaction history tab where players can review their past purchases, sales, and rental payments for complete financial transparency.
Property Customization and Value Appreciation
Letting owners customize their properties adds a personal touch and creates an upgrade economy that increases property values over time. Implement a furniture placement system that lets players purchase and position furniture items within their property interior, similar to how GTA Online handles apartment customization. Each furniture item purchased increases the property's improved value, so when the owner eventually sells, they recoup some of their investment in furnishings. Beyond furniture, offer structural upgrades like kitchen renovations, bathroom remodels, and security system installations that increase the property's base value by a percentage. Track all improvements in a property modifications table so the real estate listing can show potential buyers exactly what upgrades have been made. Commercial properties should support business-specific upgrades like display cases for a shop, kitchen equipment for a restaurant, or server racks for a tech company, tying the property system into the broader business gameplay loop. The combination of location-based market dynamics and player-driven improvements creates a rich property ecosystem where real estate becomes a legitimate wealth-building strategy rather than just a place to store items and change clothes.