# Inventory

**URL:** https://heroiclabs.com/docs/hiro/server-framework/inventory/
**Keywords:** inventory, hiro
**Categories:** hiro, inventory, server-framework

---


# Inventory

Read more about the Inventory system in Hiro [here](../../concepts/inventory/).

## Functions

### List

List the items defined as well as the computed item sets for the user by ID.

```go
userId := "userId"
category := "weapons"

items, itemSets, err := systems.GetInventorySystem().List(ctx, logger, nk, userId, category)
if err != nil {
  return err
}
```

### ListInventoryItems

List the items which are part of a user's inventory by ID.

```go
userId := "userId"
category := "weapons"

inventory, err := systems.GetInventorySystem().ListInventoryItems(ctx, logger, nk, userId, category)
if err != nil {
  return err
}
```

### ConsumeItems

Deduct the item(s) from the user's inventory and run the consume reward for each one, if defined.

Items can be consumed by using their:

- Item ID, so that any instance of the item will be consumed.
- Instance ID, so that a specific instance of the item will be consumed.

For example, if the player has 2 `potions` in the inventory, one with instance ID `x` and one with `y`if they consume items `potion: 1` either `x` or `y` might be consumed,
if they consume `x: 1` and that will specifically consume x and not touch y.

You can put `nil` on which parameter you don't wanna use.

```go
userId := "userId"
itemIds := map[string]int64{"itemId_001": 2, "itemId_002": 5}
instanceIds := map[string]int64{"instanceId_001": 1, "instanceId_002": 6, "instanceId_003": 3}
overConsume := false

updatedInventory, rewards, instanceRewards, err := systems.GetInventorySystem().ConsumeItems(ctx, logger, nk, userId, itemIds, instanceIds, overConsume)
if err != nil {
  return err
}
```

### GrantItems

Add the item(s) to a user's inventory by ID.

```go
userId := "userId"
itemIds := map[string]int64{"itemId_001": 1, "itemId_002": 2}
ignoreLimits := false

updatedInventory, newItems, updatedItems, notGrantedItemIds, err := systems.GetInventorySystem().GrantItems(ctx, logger, nk, userId, itemIds, ignoreLimits)
if err != nil {
  return err
}
```

### UpdateItems

Update the properties which are stored on each item by instance ID for a user.

This function uses conditional writes to ensure data consistency. When updating items, it will use the current inventory version if it exists (after retrieving it), otherwise it will use `*` to indicate any version is acceptable.

```go
instanceIds := map[string]*hiro.InventoryUpdateItemProperties{"itemId_001": {
  StringProperties:  map[string]string{"crafted_by": "username"},
  NumericProperties: map[string]float64{"rank": 5},
}}

updatedInventory, err := systems.GetInventorySystem().UpdateItems(ctx, logger, nk, userId, instanceIds)
if err != nil {
  return err
}
```

## Hooks

### SetOnConsumeReward

Set a custom reward function which will run after an inventory item's consume reward is rolled.

```go
systems.GetInventorySystem().SetOnConsumeReward(OnConsumeReward)

func OnConsumeReward(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID, sourceID string, source *hiro.InventoryConfigItem, rewardConfig *hiro.EconomyConfigReward, reward *hiro.Reward) (*hiro.Reward, error) {
	// Modify reward or take additional actions.
	return reward, nil
}
```

### SetConfigSource

Sets a custom callback function that is called when an item is not found in the static configuration. This is useful for dynamic item catalogs where items are generated procedurally, stored in a database, or fetched from an external service.

The function receives the `itemID` and should return the item's configuration if found.

```go
systems.GetInventorySystem().SetConfigSource(ConfigLookup)

func ConfigLookup(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID, itemID string) (*hiro.InventoryConfigItem, error) {
	// For example, look up item from database or external service
	item, err := fetchItemFromDatabase(ctx, itemID)
	if err != nil {
		return nil, err  // Actual error (database down, etc.)
	}
	if item == nil {
		return nil, nil  // Item not found
	}
	return &hiro.InventoryConfigItem{
		Name:        item.Name,
		Description: item.Description,
		Category:    item.Category,
		MaxCount:    item.MaxCount,
		Stackable:   item.Stackable,
		// ... other properties
	}, nil
}
```

**Returns:**

- `(*hiro.InventoryConfigItem, nil)` when the item is found
- `(nil, nil)` when the item doesn't exist
- `(nil, error)` for errors (database unavailable, etc.)

{{< note "important" "ConfigSource vs Personalizer" >}}
**ConfigSource** is a fallback for looking up individual items not in the static config. It's set on a specific system and returns a single item configuration.

**Personalizer** replaces the entire system config for a user. See [Personalizer](../../concepts/personalizer/) for more information.

These can work together: Personalizer runs first and may replace the entire config, then `ConfigSource` serves as a fallback for items not in that (potentially personalized) config.
{{< /note >}}

#### Property merging behavior

When retrieving inventory items, property values are merged between the item instance and its config definition. The merge follows these rules:

- Only properties that don't already exist in the item instance are added from the config source.
- Properties that exist in both the item instance and config source will retain the item instance value.

For example:

**Item instance in user's inventory:**

```json
{
  "item_id": "sword_001",
  "string_properties": {
    "rarity": "legendary",
    "owner": "player123"
  }
}
```

**ConfigSource returns:**

```json
{
  "id": "sword_001",
  "string_properties": {
    "rarity": "common",
    "damage_type": "fire",
    "durability": "100"
  }
}
```

**Final merged result:**

```json
{
  "string_properties": {
    "rarity": "legendary",
    "owner": "player123",
    "damage_type": "fire",
    "durability": "100"
  }
}
```

In this example, `rarity` keeps the item instance value (`"legendary"`) rather than the config value (`"common"`), while `damage_type` and `durability` are added from the config since they don't exist in the item instance.

This behavior allows you to set default values in your config source while preserving any customizations made to individual item instances.
