Skip to main content
Everything a scripter needs to hook their own crafting, looting, banking or trading system into DD Inventory: the full server and client export surface, the events other resources can listen to, and the handler contracts.
Every export on this page is framework-neutral. The same call behaves identically on VORP, on RSG and on a standalone server, with two exceptions that are called out where they occur: currency reads and writes depend on currencyMode, and setItemRarity only writes back into a framework item table where one exists. For the optional compatibility surfaces (vorp_inventory and rsg-inventory), see the Compatibility page. For admin commands, see Setup.

Conventions

Call server exports as exports.dd_inventory:name(...) from any server script. Unless noted, every mutating export returns a result table of the shape { ok = boolean, code = string? } plus any export-specific fields. Read-only exports return the value directly, or nil. Common error codes: INVENTORY_NOT_FOUND, ITEM_NOT_FOUND, NO_CHARACTER, NO_LICENSE, EXCEEDS_MAX, BELOW_MIN, MOVE_BLOCKED.

Server exports

Inventories

invType is a free-form string. personal, storage, vehicle, shop, guild, bank_guild, bank_accountwide, account_bank and mail_storage all carry meaning elsewhere in the system - see bindRestrictions in Setup. The returned inventory table is { data = <inventories row>, state = { [slot] = { <instances> } }, items = { <all instances> } }.

Items

simulateAddItems takes { { name = 'apple', quantity = 3, metadata = {...} }, ... } and is the correct pre-flight for any reward or purchase flow that must not partially succeed.

Item definitions and rarity

registerItemDefinition fields: item_name (required), label (required), description, stack_size (default 1), item_type (default misc), rarity (default common), weight (default 0.0), sell_value, is_usable, metadata_schema, default_metadata, bind_type (tradable | bind_on_equip | soulbound | family_bound), equipment_data. setItemRarity always writes item_definitions.rarity. On VORP it additionally writes the rarity column DD Inventory injects into the items table (frameworkSynced = true), which is what makes the change survive the boot-time re-import. On RSG and standalone there is no such column, so frameworkSynced comes back false and the definition write is the whole story. It then reloads the definition cache and pushes fresh state only to online players actually holding the item (clientsPushed). Unknown keys are accepted and reported back as warning = 'UNKNOWN_RARITY_KEY' with ok = true.

Currency and account sigils

Two character wallets and one account-wide premium balance. The keys are internal identifiers, never shown to a player - players only ever see the label you set in DDInvConfig.currencies.
In 'framework' mode these exports read and write the host framework’s wallet through dd_lib. On a standalone server there is no wallet to reach, so getCharacterCurrency returns zeros and addCurrency / removeCurrency fail. Standalone deployments must run currencyMode = 'native', see Setup. sigils is unaffected on every framework.
In 'framework' mode an offline character has no reachable wallet either, because the framework wallet is only addressable through a connected player object. Reads report zero and writes return PLAYER_OFFLINE. In 'native' mode offline characters work normally. Passing 'sigils' to addCurrency or removeCurrency transparently routes to the account-wide balance for that character’s license.

Weight

Slots and slot bonuses

Use handlers and move validators

Use handlers

Lua function references cannot be marshalled across resource VM boundaries, so a handler registered from another resource must be given as a (resourceName, exportName) pair. Three accepted shapes:
Handler contract:
Registration is last-writer-wins, so hot-reloading the owning resource is safe. opts.cooldownMs is accepted for forward compatibility but is not yet enforced.
Use handling runs with in-flight locking, automatic consume and rollback on failure, so an item that grants something can never be duplicated.

Move validators

Move validators run on every player-initiated cross-inventory move and can veto it:
Passing a key, as above, replaces any validator previously registered under that key, which keeps hot-reload clean. message, when present, is shown to the player as an error notification.

Secondary close handlers

Called when a player closes the secondary panel, so the owning resource (bank, shop, stash) can release its lock:

Per-inventory access control

Any inventory can carry an access control list in the inventory_rights table. It answers three questions per character: who may open it, who may take out of it and who may put into it.
An inventory with no entries is open to everyone. That is the default and it never changes on its own, so this feature costs an existing server nothing until you grant the first right. Granting it turns that one inventory into a whitelist; revoking the last one opens it again. The owner of an inventory (inventories.owner_char_id) always holds full rights and cannot be locked out.
The common synonyms resolve too (open, withdraw, deposit, admin, …), so hand-written rows do something sensible.
Enforcement happens in two places, both server-side:
  • Opening - the secondary-open callback and openInventoryForPlayer (which every VORP and RSG compatibility open path funnels through) check view.
  • Moving - a built-in validator registered under the key dd_inventory_rights in the same registry as registerMoveValidator, so the access list, DD Bank’s guild checks and the VORP container rules queue up together instead of contradicting each other. Cross-inventory moves need take on the source and put on the target; rearranging inside one inventory needs view.
The table is read once at start and kept in memory, so a move never costs a database query. After editing inventory_rights with raw SQL, call reloadInventoryRights(). VORP custom inventories registered with UsePermissions have their CharIdPermissionMoveTo and CharIdPermissionTakeFrom entries projected onto this table on registration, which is what makes those permissions enforced rather than merely stored.

Secondary panel

Banks, stashes, shops and storages render inside the inventory as a second panel.

Player push, open and misc

Call pushInventoryToPlayer after any out-of-band database write you performed yourself, so the open NUI does not show stale contents.

Client exports

Call as exports.dd_inventory:name(...) from any client script. Three more come from the client-side vorp compatibility layer (client/compat/vorp_client.lua). That file is not gated on the detected framework, so these exist on every framework, but only a VORP server is likely to have callers for them:

Events

Server-side broadcasts

These fire in DD Inventory’s server VM and are visible to every resource. Listen with AddEventHandler. The event names are also exposed as the constants DDEvents.INVENTORY_CREATED, DDEvents.INVENTORY_UPDATED and DDEvents.INVENTORY_TRANSFERRED in shared/dd_compat.lua. Prefer the constants over the literal strings.

Client-side broadcasts

Internal net events

dd_inventory:client:statePush, dd_inventory:sync:push (DDEvents.INV_SYNC_CLIENT), dd_inventory:open, dd_inventory:move, dd_inventory:use, dd_inventory:drop, dd_inventory:slots:purchase and dd_inventory:server:viewSecondary are the NUI’s own transport between client and server. They are listed here so you recognise them in logs - do not trigger them from other resources, use the exports above instead.

Audit log

Every add, remove, move, swap, trade and use is written to inventory_audit with a correlation id, so a single player action is traceable end to end. Pass your own correlationId into addItem or addItemToCharacterInventory to tie inventory writes to your resource’s own transaction, then read them back with /dd_debug audit <correlationId>.