Skip to main content
DD Inventory’s core is framework-agnostic and calls no framework API, so it does not need a compatibility layer to run. What these layers add is migration: they let DD Inventory replace the inventory your framework shipped with, answering that inventory’s export surface under its original name, so existing stores, banking, housing, stables, medic, police, crafting and your own custom jobs keep working without a single line changed. Two layers ship, one per framework. Both work the same way: the original resource is deleted and its item icons are copied into DD Inventory. Only the name of the deleted resource can still be missed - by a dependencies { ... } manifest line or a GetResourceState check in a third-party script. Normally you delete that line; for scripts you cannot edit, a scripts-free one-file placeholder ships in extras/ for both frameworks. There is deliberately no provide directive - FXServer’s provide does not alias runtime exports.
You may not need either layer. A standalone server has no framework inventory to replace and no legacy callers, so neither layer applies. Nothing on this page is required reading for a fresh server built on DD Inventory’s own exports.
The RSG layer is gated on DDLib.framework == 'rsg', so on a VORP or standalone server not a single line of it executes. The vorp layer is deliberately not gated: its exports register on every framework and simply go uncalled where nothing looks for them.

vorp_inventory

The complete vorp_inventory export and event surface is re-registered under both the dd_inventory and the vorp_inventory export names. That includes the legacy vorp_inventoryApi() factory, the vorpCore:* server events and the vorp_inventory:* client events.

Covered exports

VORP semantics are preserved, including the sync-return plus optional-callback duality of respond().
addItem, subItem, subItemID, subItemById, getItemCount, getItemDB, getItem, getItemById, getItemByMainId, getItemByName, getItemContainingMetadata, getItemMatchingMetadata, setItemMetadata, getUserInventoryItems, getUserInventory
canCarryItem, canCarryItems, canCarryWeapons, registerUsableItem, unRegisterUsableItem, openInventory, closeInventory, openPlayerInventory
registerInventory, removeInventory, isCustomInventoryRegistered, getCustomInventoryData, updateCustomInventoryData, updateCustomInventorySlots, getCustomInventorySlots, setCustomInventoryItemLimit, setCustomInventoryWeaponLimit, addItemsToCustomInventory, addWeaponsToCustomInventory, getCustomInventoryItems, getCustomInventoryWeapons, getCustomInventoryItemCount, getCustomInventoryWeaponCount, removeItemFromCustomInventory, removeWeaponFromCustomInventory, removeCustomInventoryWeaponById, updateCustomInventoryItem, deleteCustomInventory, AddPermissionMoveToCustom, AddPermissionTakeFromCustom, AddCharIdPermissionMoveToCustom, AddCharIdPermissionTakeFromCustom, BlackListCustomAny
createWeapon, giveWeapon, subWeapon, deleteWeapon, getUserInventoryWeapons, getUserWeapon, getWeaponBullets, getWeaponComponents, setWeaponCustomLabel, setWeaponSerialNumber, setWeaponCustomDesc, getUserAmmo, addBullets, subBullets, removeAllUserAmmo
The vorp_inventoryApi() factory plus the full vorpCore:* and vorp_inventory:Server:* event list from inventoryApiController.lua.
getUserTotalCountItems and getUserTotalCountWeapons (internal in the original), the pickup and drop API, and the client-side weapon-data exports getWeaponDefaultWeight, getWeaponDefaultDesc, getWeaponDefaultLabel, getWeaponsDefaultData, getWeaponAmmoTypes, getAmmoLabel and getServerItem - those read config/weapons.lua and config/ammo.lua tables that do not exist here.
DD Inventory has been tested against the standard VORP suite - stores, banking, medic, police, stables, housing (including the legacy inventory API), weapons v2, crafting and metabolism - plus custom scripts written against the vorp_inventory exports.

Mapping model

  • User inventory is the active character’s personal inventory, with charId resolved through the framework bridge.
  • Item name is item_definitions.item_name, identical to VORP’s items.item. The boot-time import guarantees every legacy name exists.
  • Quantity. VORP stores one row per stack with an amount; DD Inventory stores one row per physical unit spread over slots. All read paths aggregate by (item_name + metadata) so getItemCount and getUserInventoryItems look like VORP; all write paths handle stacking.
  • Item id. The VORP crafted id maps to the item_instances.item_id of the first unit in a stack group. subItemById(src, id, cb, allow, amount) removes that instance plus amount - 1 siblings from the same group.
Item shape returned to consumers:
percentage is pinned to 100 and isDegradable to false. type is 'item_weapon' for weapon definitions and 'item_standard' otherwise. Weapon shape:
A weapon is one item instance of a definition named weapon_<lowercased vorp name> (item_type = 'weapon', stack_size = 1, auto-registered on first grant). All VORP weapon fields live in the instance metadata.

Item import

On boot, DD Inventory reads SELECT * FROM items and inserts every missing name into item_definitions: Existing DD Inventory definitions are never overwritten. The available column set is probed with SHOW COLUMNS FROM items, so version differences in VORP’s schema are tolerated. Counts are printed on boot.

Limitations

The original vorp_inventory had a large client-side weapon subsystem: holster state, used and used2, weapon objects, component attachment and pickups. This layer has none of it. Using a weapon item in the DD NUI calls GiveWeaponToPed, SetCurrentPedWeapon and SetPedAmmoByType with the exact argument list vorp_inventory itself used. Holstering, dual-wield attach points, used2 semantics and weapon component visuals are not reproduced.
VORP items can decay (degradation, percentage, useExpired). DD Inventory has no such concept - every item reports percentage = 100 and isDegradable = false, so vorp_stores prices everything as pristine.
PermissionMoveTo and PermissionTakeFrom are stored in compat_custom_inventories.config and returned by getCustomInventoryData, but not enforced: they are keyed by job, and resolving a player’s job would mean calling the host framework directly, which DD Inventory does not do.CharIdPermissionMoveTo and CharIdPermissionTakeFrom are enforced when UsePermissions is set. On registration they are projected onto DD Inventory’s own inventory_rights access control list (put and take respectively), so there is one permission model rather than two competing ones. Clearing UsePermissions, or removing the last character, clears the list and reopens the container. See Per-inventory access control.Also enforced: acceptWeapons, UseBlackList / BlackListItems, whitelistItems / whitelistWeapons (the matching limited* table doubles as the allow-list) and the limitedItems / limitedWeapons per-item count ceilings. All of these are deposit-side rules in VORP, so they gate what a container accepts, not what may be taken back out of it.
VORP is weight-based (invCapacity), DD Inventory is slot-based. canCarryItems(src, amount) answers “are there at least amount free slots”. canCarryItem runs the add simulation, which is the accurate answer for the slot model. Scripts that pass a weight to canCarryItems will get a stricter answer than before.
VORP’s limit is a weight capacity for some scripts (vorp_housing passes MaxWeight) and a slot count for others. It is clamped into 10 to 100 slots, because inventories.max_slots is a TINYINT. Houses and stables configured with, for example, MaxWeight = 500 become 100-slot containers.
Opens the target’s personal inventory as the caller’s secondary panel. The original’s per-item-type limits, cooldowns and blacklist payload are ignored.
VORP’s subWeapon detaches a weapon without deleting it, so the id stays resolvable. Detached weapons are parked in an internal inventory registered as __vorp_weapon_limbo__. If that inventory is full the weapon is hard-deleted instead, and the deletion is logged.
shared = true is stored but has no effect - every VORP id maps to exactly one backing inventory, which is already shared between all characters that open it. Non-shared per-character storages (shared = false) are therefore also shared. Scripts that relied on per-character segmentation behind one id, as some vorp_stables configs do, will see one common container.

rsg-inventory

DD Inventory can replace rsg-inventory on an RSG server. Your existing rsg-* scripts keep calling exports['rsg-inventory']:AddItem(...) and friends, and DD Inventory answers them.
Verification status. The RSG code paths are written against the published rsg-inventory source and reference documentation, and pass a full Lua syntax sweep. They have not been run against a live RSG server. The VORP path is the one that is proven in production. Test on a staging server before production.

What an RSG server gets

  • DD Inventory’s own NUI, slot model, weight model, rarities, trading and currency bar, in place of the rsg-inventory interface.
  • All 29 rsg-inventory server exports, registered under both the dd_inventory and the rsg-inventory export names.
  • The rsg-inventory client export HasItem.
  • A one-way projection of the authoritative inventory into Player.PlayerData.items, so every rsg-* script that reads that table, and the client mirror rsg-core derives from it, sees the truth.
  • An automatic import of RSGCore.Shared.Items into item_definitions, re-run whenever rsg-core broadcasts a core object update.
  • Weapon handling: serial generation in the upstream format, info.quality initialisation, player_weapons bookkeeping, un-equipping a weapon that leaves the inventory, and dispatch to rsg-weapons:client:UseWeapon, UseThrownWeapon and UseEquipment with the unmodified RSG item shape.
rsg-core itself is not patched. Every access rsg-core makes to the inventory goes through an export or an event, so a script-free placeholder plus the export registrations is sufficient.

Installation differences

Follow the standard Setup, with three changes.
1

Icons live elsewhere

RSG keeps item images in html/images/, not html/img/items/:
rsg-inventory, rsg-core and rsg-weapons resolve item images relatively and need no path change. Only your own scripts that hardcode nui://rsg-inventory/html/images/ need pointing at nui://dd_inventory/img/items/.
2

Keep the name resolvable, if anything still needs it

Every exports['rsg-inventory']:* call is answered by DD Inventory, so scripts keep working. What still looks for the name is a manifest line dependencies { 'rsg-inventory' }, which stops that resource from starting, and a runtime GetResourceState('rsg-inventory') check, which makes a script quietly skip its inventory path.Normally you just delete that line. If you cannot edit the script, because it is encrypted or escrowed, copy extras/rsg-inventory/ out of this download into resources/[rsg]/. It is a single manifest with no scripts and no files, and it exists only so the name resolves. Nothing to rename, nothing to configure. Start it before dd_inventory.
3

Check four settings before first boot

See the table below.
DD Inventory creates three extra tables on an RSG server: The boot log line to look for:

Server export reference

Call as exports['rsg-inventory']:Name(...) exactly as before, or as exports.dd_inventory:Name(...). Signatures are the ones in rsg-inventory/server/exports.lua. Where the published documentation disagrees with that file, the file wins. Status values: implemented behaves as upstream for the documented contract. partial works, with a behavioural difference called out in the note. no-op is accepted and returns the upstream return type, but does nothing, because DD Inventory has already done the work. Totals: 25 implemented, 2 partial, 2 no-op, 0 not supported. slots on a stash is clamped to 100, because inventories.max_slots is a TINYINT. RSG’s own default stash size is exactly 100, so only servers that raised it are affected. CreateShop, OpenShop, RestockShop and DoesShopExist are not supported - see the limitations below.

Client export and events

Events fired towards other resources: rsg-inventory:server:closeInventory, :useItem, :updateHotbar, :SetInventoryData, :openPlayerInventory, :openStash, :openDrop, :openVending and the seven :*Trade* events are the transport protocol of the rsg-inventory web interface. DD Inventory ships its own NUI and that interface does not run, so these have no callers.
One consequence worth stating plainly: the restricted-stash prefix rules (police-, marshal-, gang-, admin-, evidence-) live only in those NUI event handlers upstream, never in the OpenInventory export. Access control on the export path is the caller’s responsibility upstream and remains so here. If you need it enforced inside the inventory, register a move validator.

Item model mapping

RSGCore.Shared.Items is imported into item_definitions on boot and again on RSGCore:Server:UpdateObject. Existing DD Inventory definitions are never overwritten, so your own balance data stays authoritative. DD Inventory adds rarity, sell_value, bind_type and equipment_data, which RSG has no concept of. Imported items start on the defaults and can be filled in with /dd_setrarity and registerItemDefinition. DD Inventory stores one row per physical unit, each carrying a slot. An RSG stack is therefore the set of instances sharing one slot, and the RSG slot is the DD Inventory slot. There is no separate slot projection table and slot addressing is stable across relogs by construction. info maps flat onto the instance metadata column: the metadata object is the info table. That makes DD Inventory’s stack matching, which compares whole metadata objects, equivalent to RSG’s rule that items with different info - specifically different info.quality - do not stack. RSG weapons are ordinary items, which makes them considerably simpler than VORP’s. Everything that identifies a specific gun lives in info:

Documentation versus source deviations

Every signature was checked against both the published rsg documentation and Rexshack-RedM/rsg-inventory at main. Where they disagree, the source code is authoritative. These are the differences found.
Overview page: AddItem(source, itemName, amount, info, slot, reason). Source, server/exports.lua:619: AddItem(identifier, item, amount, slot, info, reason).Source wins. slot is the 4th argument and info is the 5th. Confirmed twice inside the upstream repository itself: server/events/callbacks.lua:60 calls Inventory.AddItem(target, item, amount, false, serverInfo, reason), and the published Weapons page shows AddItem(source, 'weapon_revolver_cattleman', 1, nil, weaponInfo). Both put slot before info. The Functions reference agrees with the source. The Overview page is wrong.
Overview page: RemoveItem(source, itemName, amount, slot, reason). Source, server/exports.lua:761: RemoveItem(identifier, item, amount, slot, reason, isMove).Source wins. The Overview page omits the 6th argument isMove, which controls whether rsg-core:client:RemoveWeaponFromTab fires. The Functions reference lists all six.
Overview page: HasItem(source, itemName, amount). Source, server/exports.lua:396: items may be a string, an array of names, or a map of name to required amount.Source wins. All three forms are supported here.
Overview page shows a plain boolean. Source, server/exports.lua:296: returns boolean plus a reason string 'weight' or 'slots'.Source wins. The Functions reference also documents the second return value.
The export is declared UseItem(itemName, ...) at server/exports.lua:134, but rsg-core/server/functions.lua:447 calls exports['rsg-inventory']:UseItem(source, item).Upstream contradicts itself, so both forms are accepted. The first argument decides: an item name is never a number, a source always is.
Overview page names the third argument options, server/exports.lua:502 names it data. Cosmetic only, same table.
The Functions reference says it returns number, number, but server/exports.lua:251 returns 0, maxSlots when no inventory is found, and maxSlots is nil in that branch. That is an upstream latent bug. This implementation returns 0, 0 for an unknown identifier rather than 0, nil.

Limitations

Read this section before you deploy. These are deliberate boundaries, not bugs.
DD Inventory has no ground item system. ForceDropItem removes the item from the player and returns false instead of a network id, so a script that calls it directly destroys the item rather than dropping it. AddItem on a full inventory simply fails and returns false rather than force-dropping the overflow, so nothing is lost on that path. The drop-related events (createDrop, updateDrop, GetCurrentDrops, openDrop, setupDropTarget, removeDropTarget) are not provided. If your server runs a death-loot or robbery script that expects world drops, this layer is not sufficient for it.
CreateShop, OpenShop, RestockShop and DoesShopExist are not provided, and neither is the shop_stock table, restock cron, defaultstock, maxStock or minQuality. DD Inventory has no pricing model, so this is a separate product rather than a compatibility gap.
The seven rsg-inventory:server:*Trade* events are not reimplemented. DD Inventory ships its own player-to-player trade window, reached through exports.dd_inventory:requestTrade(...).
DD Inventory is authoritative and pushes a read-only projection into Player.PlayerData.items. There is no reverse channel. A resource that writes into Player.PlayerData.items directly and expects the change to persist is not supported: the write is not intercepted, and the next projection overwrites it. Upstream rsg-weapons does exactly this when it degrades or repairs a weapon. Use exports['rsg-inventory']:SetItemData(source, itemName, 'info', info) instead, which is supported and persists. Everything that reads PlayerData.items works unchanged.
DD Inventory has no degradation model. info.quality and info.lastUpdate are created where upstream creates them and then preserved verbatim, so rsg-weapons keeps full control of weapon wear and its repair loop behaves normally. What is not provided is rsg-inventory’s ambient decay: food and other items with a decay value in RSGCore.Shared.Items will not lose quality over time on their own, will never be auto-deleted at zero quality, and the decay<N> stash name suffix has no effect.
Upstream only enforces the police-, marshal-, gang-, admin- and evidence- prefixes inside its own web-interface event handlers, never in the OpenInventory export. That behaviour is mirrored. Register a DD Inventory move validator if you want the rule enforced inside the inventory itself.
RSGConfig.Money.EnableMoneyItems = true makes rsg-core model cash as inventory items and re-synchronise them on every player data update, which conflicts with DD Inventory’s currency handling. Set it to false. DD Inventory prints a boot warning if it is left on.
RSG item weights are imported one to one, so set DDInvConfig.defaultMaxWeight = 35000.0 to match RSG’s default carry capacity. DD Inventory’s own capacity is then the single authority: GetFreeWeight, CanAddItem and GetSlots answer from DD Inventory’s numbers, including purchased slot expansions, not from Player.PlayerData.weight and .slots.
inventories.max_slots is a TINYINT. RSG’s own default stash size is 100, so only servers that raised it are affected; larger values are clamped.
RSG’s citizenid is a string and DD Inventory’s owner_char_id column is an INT, so the layer mints a stable surrogate integer per citizenid in compat_rsg_citizenids. This is transparent in normal operation, but if you query DD Inventory’s tables by hand, join through that table rather than expecting a citizenid.
These paths are written against the published source and reference documentation and pass a full Lua syntax sweep, but they have not been exercised on a running RSG stack. Test on a staging server before production.