Guide 2026-05-24

FiveM Warehouse & Storage System Guide

TDYSKY

TDYSKY

Founder & Lead Developer at Agency Scripts

Storage Systems in Roleplay Servers

Every roleplay server needs a robust storage system beyond the basic player inventory. Personal inventories have limited capacity, and players accumulate items through jobs, crafting, trading, and looting that they want to keep but cannot carry at all times. Warehouse and storage systems solve this by providing physical locations in the game world where players can deposit and retrieve items. These systems serve multiple gameplay purposes: personal storage lockers give individual players extra space, organization warehouses let gangs and businesses pool their resources, and rental storage units create a property market that generates recurring revenue for the server economy. The technical implementation must handle concurrent access, weight and slot limits, access permissions, and integration with your server's existing inventory framework.

Database Schema for Storage

Design your storage database to support multiple storage types with flexible ownership and access control. Each storage unit needs a unique identifier, an owner reference that can be a player citizenid or an organization identifier, capacity limits defined by weight and slot count, and the actual inventory data serialized as JSON. Include a rental system table that tracks payment status for rented units, with automatic lockout when rent is overdue. The access control table allows owners to grant other players permission to access their storage units:

CREATE TABLE IF NOT EXISTS storage_units (
    id INT AUTO_INCREMENT PRIMARY KEY,
    unit_id VARCHAR(50) UNIQUE NOT NULL,
    unit_type ENUM('personal', 'organization', 'rental', 'property') DEFAULT 'personal',
    owner_id VARCHAR(50) NOT NULL,
    label VARCHAR(100) DEFAULT NULL,
    max_weight INT DEFAULT 100000,
    max_slots INT DEFAULT 50,
    items LONGTEXT DEFAULT '[]',
    location VARCHAR(50) NOT NULL,
    is_locked BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_owner (owner_id),
    INDEX idx_location (location),
    INDEX idx_type (unit_type)
);

CREATE TABLE IF NOT EXISTS storage_access (
    unit_id VARCHAR(50) NOT NULL,
    citizenid VARCHAR(50) NOT NULL,
    permission ENUM('view', 'deposit', 'full') DEFAULT 'deposit',
    granted_by VARCHAR(50) NOT NULL,
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (unit_id, citizenid)
);

CREATE TABLE IF NOT EXISTS storage_rentals (
    unit_id VARCHAR(50) PRIMARY KEY,
    tenant_id VARCHAR(50) NOT NULL,
    rent_amount INT NOT NULL,
    rent_interval ENUM('daily', 'weekly') DEFAULT 'weekly',
    last_paid TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    next_due TIMESTAMP NOT NULL,
    is_overdue BOOLEAN DEFAULT FALSE,
    INDEX idx_tenant (tenant_id),
    INDEX idx_due (next_due)
);

Warehouse Location Configuration

Define warehouse locations around the map with attention to roleplay context. Industrial areas near the port work well for large commercial warehouses. Residential neighborhoods suit personal storage lockers. Underground or hidden locations serve as gang stash houses. Each location needs configuration for its physical position, the type of storage units available, pricing for rental units, and the interior shell that loads when a player enters. Use GTA's interior proxies or custom MLOs for the warehouse interiors so players physically walk into a building rather than just opening a menu on the street:

Config.StorageLocations = {
    ['industrial_1'] = {
        label = 'Elysian Island Storage',
        entrance = vector3(-96.67, -1601.77, 29.41),
        blipSprite = 473,
        blipColor = 2,
        units = {
            {id = 'ind1_small_1', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
            {id = 'ind1_small_2', type = 'rental', size = 'small', maxWeight = 50000, maxSlots = 30, rent = 500, interval = 'weekly'},
            {id = 'ind1_medium_1', type = 'rental', size = 'medium', maxWeight = 150000, maxSlots = 75, rent = 1500, interval = 'weekly'},
            {id = 'ind1_large_1', type = 'rental', size = 'large', maxWeight = 300000, maxSlots = 150, rent = 3500, interval = 'weekly'},
        },
    },
    ['gang_southside'] = {
        label = 'Southside Stash',
        entrance = vector3(93.41, -1961.08, 20.82),
        blipSprite = 0,  -- No blip for hidden locations
        restrictedTo = {'ballas', 'gsf'},  -- Gang-only access
        units = {
            {id = 'gang_ss_main', type = 'organization', size = 'large', maxWeight = 500000, maxSlots = 200},
        },
    },
}

Inventory Integration

Your warehouse system must integrate seamlessly with whatever inventory framework your server uses, whether that is ox_inventory, qb-inventory, or a custom solution. The storage UI should mirror the familiar inventory interface so players instinctively understand how to move items between their personal inventory and the storage unit. Implement drag-and-drop item transfer with quantity selection for stackable items. When a player opens a storage unit, load the stored items from the database and present them alongside the player's personal inventory. All item movements must be validated server-side to prevent duplication exploits. Check that the source inventory actually contains the item being moved, that the destination has sufficient capacity in both weight and slots, and that the item is not flagged as untradeable or bound to the player. Use database transactions for item transfers so that removing from one inventory and adding to another happens atomically:

RegisterNetEvent('storage:server:moveItem', function(unitId, direction, itemName, amount, fromSlot, toSlot)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate access
    if not HasStorageAccess(Player.PlayerData.citizenid, unitId, 'full') then
        TriggerClientEvent('QBCore:Notify', src, 'Access denied', 'error')
        return
    end

    -- Load storage unit
    local unit = GetStorageUnit(unitId)
    if not unit or unit.is_locked then
        TriggerClientEvent('QBCore:Notify', src, 'Storage is locked', 'error')
        return
    end

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if direction == 'to_storage' then
        -- Player -> Storage
        local playerItem = Player.Functions.GetItemBySlot(fromSlot)
        if not playerItem or playerItem.name ~= itemName then return end
        if playerItem.amount < amount then return end

        local itemWeight = QBCore.Shared.Items[itemName].weight * amount
        local currentWeight = CalculateStorageWeight(unit.items)

        if currentWeight + itemWeight > unit.max_weight then
            TriggerClientEvent('QBCore:Notify', src, 'Storage is full (weight)', 'error')
            return
        end

        -- Atomic transfer
        Player.Functions.RemoveItem(itemName, amount, fromSlot)
        AddItemToStorage(unitId, itemName, amount, playerItem.info, toSlot)

    elseif direction == 'from_storage' then
        -- Storage -> Player
        local storageItem = GetStorageItemBySlot(unit.items, fromSlot)
        if not storageItem or storageItem.name ~= itemName then return end
        if storageItem.amount < amount then return end

        if not Player.Functions.AddItem(itemName, amount, toSlot, storageItem.info) then
            TriggerClientEvent('QBCore:Notify', src, 'Inventory full', 'error')
            return
        end

        RemoveItemFromStorage(unitId, itemName, amount, fromSlot)
    end

    -- Refresh UI for all viewers
    RefreshStorageViewers(unitId)
end)

Rental Payment System

Rental storage units require a recurring payment system that automatically charges tenants and handles overdue accounts. Run a server-side scheduled task that checks all rental units at a regular interval, processes payments for units that are due, and locks units that are overdue. When a payment is due, attempt to deduct the rent amount from the tenant's bank account. If the bank account has insufficient funds, mark the rental as overdue and send the player a notification. Give overdue tenants a grace period of a few days before locking their unit. A locked unit prevents the tenant from accessing their stored items until the outstanding balance is paid. If the rental remains unpaid beyond a longer threshold, the contents could be auctioned off or cleared, creating interesting secondary gameplay. Implement a payment history log so tenants can review their rental charges and payment dates through the storage management interface.

Organization and Shared Storage

Organizations like gangs, businesses, and government departments need shared storage that multiple members can access with appropriate permission levels. The organization leader has full control and can add or remove members from the access list, set individual permission levels, and view audit logs of all items deposited or withdrawn. Regular members might have deposit-only access where they can add items but cannot remove them, which is useful for gang members contributing materials to a shared pool. Trusted members get full access to both deposit and withdraw. Implement a comprehensive audit log that records every item movement in and out of shared storage, including the player who performed the action, the item and quantity, and the timestamp. This audit trail helps organization leaders track who is contributing and who might be stealing from the shared pool. The log should be viewable through the storage UI with filtering options by player, item type, and date range.

Capacity Upgrades and Tiers

Allow players to upgrade their storage capacity through an upgrade system that provides a progression incentive. Start with small storage units that have limited weight and slot capacity, then offer upgrade tiers that increase these limits for a price. Each upgrade tier should cost progressively more, creating a meaningful money sink for the server economy. Upgrades can increase maximum weight, add additional slots, or unlock special features like temperature-controlled storage for perishable items or reinforced security that makes the unit resistant to break-ins. Display the current tier and available upgrades in the storage management interface, with clear pricing and the benefits each tier provides. Consider tying some upgrade tiers to achievements or playtime milestones rather than pure monetary cost, rewarding dedicated players with premium storage benefits they cannot simply buy with money.

Security and Break-In Mechanics

Storage units should not be completely safe from criminal activity. Implement a break-in mechanic that allows players to attempt to access storage units they do not own, creating risk for stored valuables and generating heist scenarios. The break-in process should require specific tools like lockpicks or electronic bypass devices, take a significant amount of time with visible and audible indicators that alert nearby players, and trigger a notification to the storage owner and law enforcement. Higher-tier storage units and those with security upgrades should be progressively harder to break into, requiring better tools and more time. If a break-in succeeds, the criminal gets temporary access to the storage inventory and can take items, but the system should log the break-in and provide the owner with evidence such as partial identifier information that can be investigated by police. This creates a full gameplay loop around warehouse raids, investigation, and consequences that enriches the server's criminal and law enforcement dynamics.

Share this article

Ready to upgrade your server?

Check out our premium FiveM scripts in the Agency Scripts store or join our Discord community for support and updates.