Skip to main content
sh-housing is a server-authoritative, database-backed housing system for RedM. It supports VORP and RSG from one codebase and provides residential properties, hotel rooms, buy/rent/finance contracts, furniture placement, property storage, access sharing, realtor workflows, and sh-doorlocks integration. This page covers both routine configuration and source-level customization. Back up config.lua, the data/ directory, and all sh_housing_* database tables before changing a production installation.

System overview

Player housing

Buy, rent, or finance properties; use storage; furnish rooms; and manage keys.

Realtor tools

Create property boundaries, edit listings, broker contracts, map doors, and manage the realtor ledger.

Hotels

Configure timed rooms with separate prices, cycles, storage limits, interiors, and doors.

Content packs

Seed properties, hotels, interiors, furniture, and door mappings from editable data files.

Installation

1

Install dependencies

Install oxmysql, your framework core, and the matching inventory. Install sh-doorlocks and sh-society when using the default integrations.
2

Keep the resource name unchanged

The folder must be named exactly sh-housing. The resource stops itself when started under another name.
3

Import the schema

Run sh-housing/sql/install.sql against the same database used by your framework and oxmysql.
4

Configure the resource

Review config.lua, especially the framework, economy, realtor job, storage, door provider, interaction mode, and content synchronization sections.
5

Configure administrator access

Grant the shhousing.admin ACE or add the relevant framework groups, jobs, or gangs to Config.AdminAccess.
6

Use a deterministic start order

Start dependencies before housing.
server.cfg
7

Verify startup output

Confirm that the console reports successful initialization, the selected framework, content seed totals, and the active door backend.
Do not run another housing resource against the same properties at the same time. Duplicate interaction points, storage identifiers, ownership logic, and door state writers can conflict.

Commands

All command names can be changed in Config.Commands.
config.lua

Configuration guide

Config.Framework accepts auto, vorp, or rsg. Explicit selection is useful when both framework resources exist in a development server.
config.lua
Enable debugging temporarily while diagnosing interactions, shell teleporting, door state, or target registration. Disable it for normal production use to reduce console noise.
Config.Security is enforced server-side. These values are not only visual ranges.
  • EventCooldownMs throttles repeated event calls.
  • MaxInteractDistance is the general property/hotel action range.
  • MaxPropertyCreateDistance limits how far a realtor can create or update content.
  • MaxBuildDistance and MaxBuildHeightDelta constrain furniture placement.
  • MaxBuildMoveDelta limits one placement update.
  • MaxRequestPerMinute is the per-player RPC budget.
Increase values only when a large custom MLO or unusual interior layout requires it. Do not use large distance limits to work around incorrectly placed markers.
A single realtor job or multiple job names are supported.
config.lua
BrokeredContractsOnly = true prevents ordinary players from bypassing the realtor workflow. When RequireNearPropertyForContracts is enabled, the realtor and buyer must complete the contract near the property marker.
Access checks include console, ACE, framework group, job grade, and gang.
config.lua
server.cfg
Config.Economy.DefaultCurrency supports cash, gold, allbanks, or a valid framework-specific money key.Purchase tax applies to buy and finance contracts. Rent tax applies to each rent cycle. Hotel tax applies to the initial hotel charge.
config.lua
A buy or finance listing must have a non-zero price. The contract service rejects a zero-total purchase instead of transferring a free property accidentally.
Offline rent and finance collection edits the framework character record directly. Confirm table and column names against your framework version before enabling it.
config.lua
Config.Storage supplies defaults when a property or hotel data entry does not define its own values.Config.Furniture controls placement limits, movement precision, render batching, furniture-store locations, wallet resale, and animal roaming.Keep RenderSpawnPerTick conservative on servers with large catalogs or densely furnished properties. Increasing render distance makes more objects eligible to exist on each client and can materially increase client load.
config.lua
auto uses sh-doorlocks when the resource is started and otherwise selects the internal backend. Explicitly set sh-doorlocks if missing doorlocks should be treated as a deployment error rather than silently falling back.Enable RSGManagedLockMode when an RSG character identifier cannot be converted to the numeric character ID expected by the door resource. In managed mode, housing permission checks remain authoritative.
Config.Target.Mode accepts target, ingame, text3d, or auto.
  • target uses the configured target resource.
  • ingame uses native RedM prompt groups.
  • text3d draws world text and listens for configured keys.
  • auto prefers the target resource and falls back to FallbackMode.
If you replace ox_target, update Config.Target.Resource and confirm the target resource exposes compatible zone functions.
config.lua
The watcher reloads content only when a managed file signature changes. The minimum interval is 5 seconds.forceUpdateProperties = true means a changed content file can replace database-backed fields for matching property UIDs. Runtime-created storage marker metadata is preserved, but you should still treat forced synchronization as a write operation and keep a database backup.
The default setup sends housing revenue to sh-society.
config.lua
Set SocietyId when the society identifier differs from the realtor job name. Prefer correct server.cfg ordering over StartIfMissing = true.

Content loading and data ownership

The content service loads these files by default:
server/services/content_service.lua
The wildcard entries in fxmanifest.lua make files available to the resource, but they do not automatically make the content service parse every JSON file. Add new file paths to MANAGED_CONTENT_FILES when splitting content into multiple files.
For example:
server/services/content_service.lua
Files are merged by stable identifiers. Property and interior-property entries use uid; furniture and interior presets use id.

Source files versus database rows

The JSON content files are seed and synchronization sources. Live ownership, contracts, access grants, placed furniture, and payment history are stored in MySQL.
Do not put an owner character ID or active contract ID in a content JSON file. Ownership is transactional runtime state and should be changed through housing contracts or an administrative transfer.

Editing property data

Edit data/properties/properties_data.json to define repeatable property templates.
data/properties/properties_data.json

Property field reference

buildZone can be used instead of propertyLine. The preferred normalized form is:
Use the in-game property-line editor for the first draft, export or copy the resulting coordinates, and then clean the numbers in JSON. This is less error-prone than estimating a polygon manually.

Editing doors

Door handling has three distinct layers:
  1. data/doors/door_index.lua identifies known world doors for automatic discovery.
  2. A property’s doorData stores which doors belong to that property.
  3. sh-doorlocks or the internal backend owns the active lock state after the property becomes occupied.

Property door lifecycle

  • The in-game property creator attempts to discover up to 64 doors inside the property boundary.
  • Discovered mappings are stored with the property.
  • An unsold property has no active doorlock records attached.
  • When a buy, rent, or finance contract assigns an owner, housing creates or updates active door records and grants the owner access.
  • Access changes rebuild the door access payload.
  • When ownership is removed, housing removes the active door records but retains reusable mapping data without database door IDs.

Add a custom MLO door to the index

Add an entry to data/doors/door_index.lua using this structure:
data/doors/door_index.lua
Use the actual door-system hash, not only the object model hash. Coordinates must point to the physical door object. A correct model with incorrect coordinates can make entity fallback select the wrong nearby object.

Define a single door mapping

Define multiple property doors

Use the door override file

data/properties/property_doors_overrides.json lets you keep generated door mappings separate from a large property pack. Entries match a property by uid.
data/properties/property_doors_overrides.json
The admin panel can map doors and export database mappings into this override file. Commit the exported file with your server content so mappings survive database rebuilds.
Avoid manually copying doorId values between servers. Those IDs refer to environment-specific database rows. Portable content should contain doorData; allow housing to create the active door IDs.

Editing interior and shell data

data/interiors/interiors_data.json can contain reusable presets, property-style interior entries under properties, or both.

Reusable teleport preset

data/interiors/interiors_data.json
Use this format for an existing world interior where entry and exit are absolute teleport points.

Spawned shell property

data/interiors/interiors_data.json

Shell coordinate rules

  • originCoords is where the shell object is spawned.
  • entryCoords is where the player appears inside the shell.
  • exitCoords is the in-shell interaction point that returns the player to property coords.
  • spawnDepth is a fallback Z offset when no absolute origin is supplied.
  • entryOffset and exitOffset are supported fallbacks relative to the shell origin.
  • headingOffset, entryHeadingOffset, and exitHeadingOffset can adjust orientation.
  • onlyInside = true restricts build validation to the interior zone when one is configured.
Do not use the exterior property marker as a shell entryCoords unless that is intentional. The exterior marker starts the teleport; the shell entry should normally be at the spawned shell’s underground or isolated coordinates.

Editing hotel data

Edit data/hotels/hotels_data.json.
data/hotels/hotels_data.json
Hotel entries use cycleHours, while residential rent uses rentCycleDays. An enabled hotel with mapped door data synchronizes its active door through the hotel contract lifecycle.

Editing the furniture catalog

Edit data/furniture/furniture_data.json.
data/furniture/furniture_data.json
Do not rename a furniture id after players have purchased it. Wallet and placement rows reference the catalog ID. Add a new item and migrate old references deliberately if an ID must change.

Metadata reference

metadata objects let content authors attach structured information without adding database columns or changing the base content schema. Metadata is JSON, so values can be strings, numbers, booleans, arrays, or nested objects. Use metadata for optional presentation details, grouping, integration identifiers, and runtime hints. Keep required business data such as prices, coordinates, owner IDs, and contract status in their documented fields.

Furniture metadata and custom images

The furniture catalog UI recognizes these preview keys inside each item’s metadata object: Use metadata.imageUrl rather than a top-level imageUrl. The furniture normalizer preserves the metadata object, while its normalized top-level output only includes id, model, label, category, price, tags, and metadata.
data/furniture/furniture_data.json
The preview resolution order is:
  1. metadata.image or metadata.imageUrl.
  2. The automatic model image generated from the model name.
  3. A metadata fallback URL.
  4. A hardcoded fallback in CATALOG_PREVIEW_FALLBACKS inside ui/app.js.
  5. The configured default fallback image.
If no image loads, the catalog displays Preview unavailable for this model and still allows normal purchasing and placement.
Use HTTPS image URLs with stable filenames and reasonable dimensions. WebP or optimized JPEG files reduce NUI loading time. Avoid URLs containing quotes or short-lived authentication tokens.

Hosting furniture images inside the resource

You can ship preview images with sh-housing instead of using an external CDN.
  1. Create ui/images/furniture/.
  2. Add the image files to the files block in fxmanifest.lua.
  3. Reference them relative to ui/index.html.
fxmanifest.lua
Restart sh-housing after adding new files to fxmanifest.lua. Refreshing only the JSON does not add newly introduced image files to the resource manifest.

Furniture tags and custom grouping

Furniture supports a top-level tags array and any custom fields inside metadata.
The base content service preserves these values, but the default catalog search currently matches only id, label, model, and category. Tags and arbitrary metadata do not change behavior until another script or a UI customization consumes them. To include top-level tags in catalog searches, update the search haystack in server/services/content_service.lua:
server/services/content_service.lua
This is a server-side catalog filter change. Restart sh-housing after modifying the Lua service.

Furniture runtime metadata

Some furniture metadata fields affect placed entities: For ordinary catalog animals, set category to animals; the server automatically applies entityType = 'ped' and animalFurniture = true to the placement. Do not manually set walletConsumed or storePriceCash in catalog metadata because those fields represent transaction state.
Arbitrary catalog metadata is retained on the catalog item, but it is not all copied into placed-furniture metadata. The placement service automatically carries category and sourceSection; transaction and placement fields are then added separately. Copy additional catalog metadata explicitly in furniture_service.lua if your placement runtime needs it.

Property metadata

Property metadata contains several recognized fields: You may add your own fields for other resources:
Custom property metadata does nothing automatically. Read it through GetProperty or GetProperties from your integration resource and enforce any additional rule server-side.

Interior metadata

Interior metadata supports shell-related aliases and presentation fields. Recognized keys include:
  • shell.model, interiorModel, interior_model, or Interior_Model
  • shell.originCoords, originCoords, or spawnCoords
  • shell.entryCoords or entryCoords
  • shell.exitCoords or exitCoords
  • shell.entryOffset or entryOffset
  • shell.exitOffset or exitOffset
  • shell.spawnDepth or spawnDepth
  • headingOffset, entryHeadingOffset, and exitHeadingOffset
  • Interior_SubModel
  • page
Prefer the nested metadata.shell structure for new content because it keeps shell-specific fields grouped together.

Access metadata

Access grants accept arbitrary metadata. Job-based door rules recognize minGrade:
Housing uses minGrade when building sh-doorlocks job-grade rules. Other custom access metadata is stored for integrations and auditing but does not grant additional permissions by itself.

Contract and door metadata

Contract metadata stores terms and system-generated finance/tax state. Door metadata sent to sh-doorlocks identifies the managing system, entity kind, property/hotel ID, and door index. Treat these as system-owned fields. Extend them only after checking the corresponding contract or door bridge service, and do not replace the complete metadata object during a partial update unless you intend to remove existing system state.

Localization

Locale files are located under locales/. To add French:
  1. Copy locales/en.lua to locales/fr.lua.
  2. Keep every message key unchanged and translate only the string values.
  3. Add locales/fr.lua to shared_scripts in fxmanifest.lua.
  4. Set Config.Locale = 'fr'.
locales/fr.lua
Some NUI labels are translated in ui/app.js. Add matching translations there when introducing a language that must localize the full web interface.

Access grants

The implemented access types are: The implemented subject types are char, job, group, and gang.
expiresAt is a Unix timestamp in seconds. Expired grants are ignored and periodically cleaned from the database.

Server exports

Programmatic property example

Database tables

Avoid editing owner, contract, access, door ID, or wallet columns directly while the resource is running. Services cache these records in memory. Direct SQL can leave the cache inconsistent until a resource restart or administrative rebuild.
1

Use a staging database

Test schema and content synchronization against a copy of production data.
2

Create an unpublished property

Verify marker position, polygon bounds, storage point, and interior teleporting.
3

Check automatic door mappings

Confirm the property panel shows the expected door count. Remove unrelated gates or neighboring doors before publishing.
4

Test every contract path

Test buy, rent, finance, termination, missed payment, eviction, and repossession with non-administrator characters.
5

Verify permissions

Test owner, key-only, stash-only, build-only, manage, realtor, and administrator behavior separately.
6

Restart all relevant resources

A full dependency-order restart catches issues hidden by hot reloads or stale client state.

Troubleshooting

  • Validate the JSON syntax.
  • Confirm the file is listed in MANAGED_CONTENT_FILES.
  • Confirm the property has a non-empty unique uid.
  • Check console output for content parsing or database errors.
  • Run Sync Content Packs from the admin panel or restart sh-housing.
  • If the UID already exists, review forceUpdateProperties behavior.
  • Add the door to data/doors/door_index.lua or the corresponding sh-doorlocks index.
  • Verify door hash, model hash, model name, and world coordinates.
  • Confirm the door lies inside the property’s polygon and vertical range.
  • Use Auto Door (Nearest) while standing at the door when polygon discovery is unsuitable.
  • Use Set Door Manual when the MLO does not expose a discoverable indexed door.
  • Confirm Config.Doors.Provider resolves to the intended backend.
  • Confirm sh-doorlocks starts before sh-housing.
  • Press Sync Door and record the exact error.
  • Check for an existing sh-doorlocks row with the same hash and coordinates.
  • Confirm the housing property has doorData and receives a persisted doorId after sale.
  • If the lock state changes in the panel but the object remains movable, re-check the MLO door hash/model/coordinates.
  • Confirm the property owner character ID matches the current selected character.
  • On RSG, verify the numeric door character ID mapping.
  • Enable RSGManagedLockMode if identifiers cannot be converted safely.
  • Re-sync the property door after ownership or access changes.
  • Confirm build mode is active for the selected property.
  • Check the exterior or interior build polygon and its Z limits.
  • Check MaxBuildDistance, MaxBuildHeightDelta, and MaxBuildMoveDelta.
  • Confirm the player has owner, manage, or build access.
  • Verify the catalog model exists and streams correctly.
  • Confirm the model name is valid and streamed.
  • Verify originCoords, entryCoords, and exitCoords are in the same shell coordinate space.
  • Test the shell without offsets before adding heading offsets.
  • Adjust Config.Interiors.Lighting only after the shell position is correct.
  • Do not use lighting intensity to hide a shell that spawned at the wrong coordinates.
Runtime content synchronization may force JSON values back into matching database rows. Disable the relevant forceUpdateProperties or forceUpdateHotels option, or make the JSON source authoritative and apply the change there as well.Door mappings edited in-game can be exported to property_doors_overrides.json so they become part of authoritative content.
  1. Define properties, hotels, and interiors in source-controlled JSON.
  2. Use unpublished listings while measuring markers, polygons, shell points, and doors.
  3. Use in-game auto mapping for doors, then export mappings to the override file.
  4. Validate JSON and JavaScript before deployment.
  5. Seed into a staging database and complete contract/access tests.
  6. Back up production tables.
  7. Deploy data and scripts together, then restart dependencies in order.
  8. Monitor initialization, content-sync, contract, and door-sync logs.
Treat stable UIDs and catalog IDs as public API. Coordinates, labels, prices, and metadata can evolve; identifiers should remain stable after release.