Every game with items needs an inventory system. Most developers build one from scratch, discover the edge cases three months later, and wish they had planned the architecture differently from the start.
This guide covers the architecture decisions that separate a production-ready inventory system from a prototype that falls apart at scale. We will work entirely in Blueprints using patterns that are clean, modular, and replication-ready.
Architecture Principles
Before writing a single node, establish three principles that will save you from refactoring later.
Actor Components, Not Monolithic Actors
Your inventory system should be an Actor Component, not baked into your character class. An Actor Component can be attached to any actor: players, NPCs, storage chests, enemy corpses, vehicles, or shops.
Create a BP_InventoryComponent that inherits from UActorComponent. This component owns the inventory data and exposes the public API. The owning actor does not need to know how inventory works internally. It just calls AddItem(), RemoveItem(), and GetItems().
This matters because you will inevitably need inventory on actors you did not originally plan for. A modular component means adding inventory to a new actor type is drag-and-drop, not a refactor.
Blueprint Interfaces for Communication
Your inventory component should not know about your UI, your crafting system, your equipment system, or your save system. It communicates through Blueprint Interfaces.
Create these interfaces:
- BPI_InventoryOwner: Implemented by any actor that has an inventory component. Functions:
GetInventoryComponent(),CanReceiveItem(ItemData) -> bool. - BPI_InventoryObserver: Implemented by anything that needs to react to inventory changes. Functions:
OnInventoryChanged(ChangeType, ItemData, Quantity),OnInventoryFull().
When the inventory changes, the component broadcasts through the observer interface. The UI listens and updates. The quest system listens and checks conditions. The save system listens and marks dirty. None of these systems have direct references to each other.
Data-Driven Item Definitions
Items should be defined in Data Assets or Data Tables, not hardcoded in Blueprints.
Create a PrimaryDataAsset subclass called DA_ItemDefinition with these properties:
ItemID(FName): Unique identifierDisplayName(FText): Localization-ready nameDescription(FText): Localization-ready descriptionIcon(TSoftObjectPtr<UTexture2D>): Soft reference to avoid loading all icons at startupMaxStackSize(int32): How many can stack in one slotWeight(float): For weight-based inventory limitsItemCategory(EItemCategory): Enum for filtering and sortingItemTags(GameplayTagContainer): Flexible tagging for queries
Your inventory stores references to these definitions plus runtime data (current stack count, durability, custom properties). The definition is the template. The inventory entry is the instance.
Core Inventory Features
The Inventory Data Model
Each inventory slot is a struct:
S_InventorySlot:
- ItemDefinition: DA_ItemDefinition (reference to the item type)
- Quantity: int32 (current stack count)
- SlotIndex: int32 (position in the inventory grid)
- InstanceData: Map<FName, FString> (custom per-instance data like durability, enchantments)
The inventory component holds a TArray of these structs. Fixed-size arrays work for grid inventories (set a max slot count). Dynamic arrays work for list inventories.
Adding Items
The AddItem function handles these cases:
- Check if the item can stack with an existing slot. Iterate through occupied slots, find one with the same ItemDefinition that has room in its stack. Add quantity up to MaxStackSize.
- If stacking cannot absorb the full quantity, find an empty slot. Place remaining quantity in a new slot.
- If no empty slots exist, return the overflow quantity. The caller decides what to do with items that do not fit (drop on ground, reject pickup, expand inventory).
Always return an integer indicating how many items were actually added. This prevents the common bug where a pickup disappears but the item was never added because the inventory was full.
Removing Items
RemoveItem takes an ItemDefinition and quantity. It searches slots in reverse order (removing from the most recent stack first feels natural to players) and decrements quantities. Empty slots are either cleared immediately or marked empty for the UI to handle.
Return the number actually removed. If the player needs to spend 5 iron ore for crafting but only has 3, the caller needs to know the operation failed before modifying game state.
Querying
Provide these query functions:
HasItem(ItemDefinition, Quantity) -> bool: Can the player afford a crafting recipe?GetItemCount(ItemDefinition) -> int32: How many total across all stacks?GetItemsByCategory(EItemCategory) -> Array<S_InventorySlot>: Filter for UI tabs.GetItemsByTag(GameplayTag) -> Array<S_InventorySlot>: Flexible queries like "all equippable items" or "all quest items."GetTotalWeight() -> float: For weight-based limits.GetEmptySlotCount() -> int32: For pickup UI ("Inventory 24/30").
Sorting and Filtering
Implement sort functions that rearrange the slot array: by category, by name, by quantity, by weight, by recency. Sorting is a UI concern, so trigger it from the UI layer, not automatically.
Consolidation is separate from sorting: merge partial stacks of the same item into full stacks. Run consolidation before sorting for clean results.
UI Binding
The inventory UI should be a separate Widget Blueprint that observes the inventory component through the BPI_InventoryObserver interface.
Do not poll. Do not check inventory state every frame. Bind UI updates to the OnInventoryChanged event. When the inventory changes, the component fires the event, the UI rebuilds only the affected slots.
For the slot widget, use a UserWidget that displays the item icon, quantity, and rarity border. Implement drag-and-drop by overriding OnMouseButtonDown, OnDragDetected, and OnDrop. The drag payload carries the source slot index. The drop target reads it and calls SwapSlots() or MoveItem() on the inventory component.
Tooltip pattern: On hover, spawn a tooltip widget that reads from the ItemDefinition Data Asset. Display name, description, stats, and any instance-specific data (durability, enchantments). Use soft object references for icons so tooltips do not cause hitches loading textures.
Replication for Multiplayer
If your game is multiplayer or might become multiplayer, plan for replication from the start. Retrofitting networking onto a single-player inventory is one of the most painful refactors in game development.
Server Authority
The server owns the inventory truth. Clients request changes through Server RPCs:
Server_RequestAddItem(ItemDefinition, Quantity)Server_RequestRemoveItem(ItemDefinition, Quantity)Server_RequestMoveItem(FromSlot, ToSlot)
The server validates the request (does the player actually have this item? is the trade legal? is the crafting recipe valid?) and applies the change. The updated inventory replicates to the owning client.
Replication Strategy
You have two options:
Replicated TArray: Mark the inventory slot array as Replicated with RepNotify. When it changes on the server, the engine replicates the entire array to the owning client. Simple but bandwidth-heavy for large inventories.
RPC-based delta updates: The server sends only the changed slots via Client RPCs. More complex to implement but far more bandwidth-efficient. For inventories larger than 20 slots, this approach is worth the extra work.
Prediction
For responsive UI, apply changes optimistically on the client, then reconcile when the server response arrives. If the server rejects the change (anti-cheat detected an invalid operation), roll back the client state. This prevents the input lag that makes networked inventory feel sluggish.
Common Pitfalls
Not handling edge cases in stacking. What happens when you add 150 items with a max stack size of 99? You need to fill one stack to 99 and create a new stack of 51. What if there is only one empty slot? Fill the stack to 99, put 51 in the new slot. What if there are zero empty slots but an existing stack has room for 30? Add 30, return 120 as overflow.
Coupling inventory to the character class. The moment you need a storage chest, you are refactoring. Use components from the start.
Storing full item data instead of references. Your inventory should store a reference to the Data Asset plus instance data, not a copy of all item properties. When you update an item's description in the Data Asset, it updates everywhere automatically.
Ignoring save/load from the start. Your inventory needs to serialize to and deserialize from a save format. Design the data model with serialization in mind. FName-based ItemIDs are serialization-friendly. Direct object references are not.
When to Build vs. Buy
Building an inventory system from scratch takes two to four weeks for a competent Blueprint developer. That includes the data model, core logic, UI, drag-and-drop, sorting, filtering, and basic save/load. Add another two weeks for multiplayer replication and prediction. Add another week for crafting integration.
That is five to seven weeks before your first unique gameplay feature exists.
The Blueprint Template Library includes a complete inventory and crafting system built on exactly the architecture described in this post: Actor Component-based, Blueprint Interface communication, Data Asset-driven items, replicated with server authority, and integrated with save/load. It also connects to the library's other systems: crafting consumes inventory items, equipment modifies stats, quest objectives check inventory state, and the save system persists everything.
If your game's inventory system is standard (slots, stacking, categories, drag-and-drop, crafting), using a production-tested template saves five or more weeks. If your game has unique inventory mechanics (spatial grid tetris, physics-based bags, real-time degradation), build from scratch using the architecture patterns in this guide, and you will have a solid foundation.
Either way, the architecture matters more than the implementation. Get the component structure, interface communication, and data model right, and the rest follows.