FiveM housing and apartments: shells, instances, stashes and keys

How a FiveM housing system works: shell and MLO interiors, instancing with routing buckets, a stash per house, keys and permissions, and saving ownership in the database.

The symptom: two players enter two different apartments and see each other, the stash is shared between every house, or ownership disappears when the server restarts.

A housing system is four things working together: an interior, a way to separate players, a place to store items, and a record of who may enter. This article explains each part and how they connect.

Shell or MLO interior

You have two ways to give a house an inside:

Type How it works Good for
Shell A prop spawned at coordinates, removed on exit Apartments, many identical homes
MLO A permanent interior on the map Unique houses and mansions

A shell is a model you load, place at a fixed location (often high in the sky or far away, under the map), and delete when the player leaves. Because the same shell can be spawned for any number of houses, shells are how apartment buildings are done. An MLO stays on the map, so it is limited to one place. To install one, see installing an MLO, and if it breaks the map around it, MLO map conflicts.

Instancing with routing buckets

If every apartment uses the same shell at the same coordinates, then everybody who enters stands in the same room. The fix is to give each house its own routing bucket, an isolated copy of the world on the server. Players in different buckets cannot see each other or each other's entities. The idea in one sentence: house number 12 uses bucket 12 (plus an offset so it never collides with other uses).

lua
-- server
local HOUSE_BUCKET_OFFSET = 1000

RegisterNetEvent('housing:enter', function(houseId)
    local src = source
    if not canEnter(src, houseId) then return end   -- ownership or key check

    SetPlayerRoutingBucket(src, HOUSE_BUCKET_OFFSET + houseId)
    TriggerClientEvent('housing:spawnShell', src, houseId)
end)

RegisterNetEvent('housing:leave', function()
    local src = source
    SetPlayerRoutingBucket(src, 0)
    TriggerClientEvent('housing:removeShell', src)
end)

Everyone who visits that house must be put in the same bucket, and every exit must set the bucket back to 0. Handle disconnects and script restarts too, so no player stays in a bucket that nobody else can reach. All the natives are explained in routing buckets.

Tip: if you spawn the shell prop on the client, only that player sees it. For a shell that visitors also see, each visitor spawns it for themselves, or the server creates it in the same bucket.

A stash for every house

Each house needs its own storage. With ox_inventory, register a stash per house, with an id that includes the house id:

lua
-- server
exports.ox_inventory:RegisterStash('house_' .. houseId, 'House storage', 50, 100000, ownerId)

Then open it with the same id on the client:

lua
exports.ox_inventory:openInventory('stash', 'house_' .. houseId)

If the id is the same for every house, every house shares one stash, which is the symptom from the start of this article. Add the permission check on the server before the stash is opened, and see ox_inventory stashes for slots, weight and who may open one.

Keys and permissions

Ownership is not the only way in. Plan for these cases:

  • Owner: always enters, can sell the house and give out keys.
  • Key holders: friends or tenants who may enter and use the stash, but cannot sell.
  • Guests: visit when the owner is inside and lets them in (a doorbell).
  • Police: a raid or an official entry, if your server has one.

Keep the keys as data rather than an item you can drop: a list of character identifiers per house. Check the list on the server every time someone tries to enter or open the stash.

Saving ownership in the database

Everything that must survive a restart goes in the database. A simple structure is one table for houses and one for the keys:

sql
CREATE TABLE IF NOT EXISTS houses (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  owner VARCHAR(64) DEFAULT NULL,
  price INT NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS house_keys (
  house_id INT NOT NULL,
  holder VARCHAR(64) NOT NULL,
  PRIMARY KEY (house_id, holder)
);

Store the character identifier your framework uses for characters (for example the identifier of the character rather than the player licence), so ownership follows the character. Load the data when the resource starts and keep a copy in a server table for fast checks, writing back to the database on every change. Queries from Lua are in oxmysql queries.

Money flows matter too: the purchase, the rent and the sale refund should all be handled on the server, and they act as a money sink, as described in balancing your server economy.

Common mistakes

  • Not resetting the bucket on exit, death, disconnect or restart.
  • Same stash id for all houses.
  • Checking ownership on the client only.
  • Ownership kept in memory and lost at restart.
  • Spawning the shell more than once when the player re-enters quickly, leaving duplicate props.

Checklist

Symptom Fix
Players in different houses see each other Give each house its own routing bucket
Player stuck in an empty world Reset the bucket to 0 on exit, death and disconnect
Every house shares one stash Use a stash id that includes the house id
Ownership lost on restart Save it in the database
Anyone can enter Check owner or key on the server
Duplicate shell props Delete the shell on exit and guard against double spawns

Quick answers

What is the difference between a shell and an MLO interior?

An MLO is a real interior placed on the map at fixed coordinates. A shell is a prop you spawn on demand, usually far from the map, and remove when the player leaves.

Why can players see each other's houses?

Every house interior uses the same coordinates, so players in different houses stand in the same space. Put each house in its own routing bucket so they are separated.

Where should house ownership be saved?

In the database, with the house id and the owner's character identifier, and a separate table or column for the people who hold keys. Never keep ownership only in memory.

Scripts that skip this problem

CCTV Security CamerasPlaceable cameras, a live multi-view tablet and printed evidence photos.View script β†’Shop CreatorBuild a shop in under a minute β€” owners, employees, vaults and robberies included.View script β†’Item Creator V2Create usable items with animations, props, effects and more β€” without writing code.View script β†’

Keep reading