§ How to · with Picoo

How to make a Roblox inventory system (with AI)

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

A Roblox inventory system needs four pieces: shared item definitions, stacking, actions the server checks, and saves. Ask Picoo for an inventory and its inventory recipe builds all four: a 20-slot grid that opens on B, four starter item definitions, and a server function that is the only way items get in.

9 files · 1256 lines · one recipe call. 852 of those lines are PicooFeedback, the shared module behind the item popups.

20-slot grid

A centered panel of slot buttons, opened and closed with B. Each slot shows the item's name, plus its count when there is more than one. The slot count is a parameter from 4 to 60.

Item stacking

Adding an item tops up existing stacks first, then fills empty slots. Each item has its own stack size, capped by max_stack (20 by default).

Use, move, drop

Click a slot to select it (it gets a green outline), click it again to use one item, or click another slot to swap the two. Right-click deletes the whole stack. The server checks every request.

Items come from the server

Server code grants items with _G.InventoryServer.add(player, id, count), which returns how many fit and shows a '+2 Health Potion' style popup. The one remote the server listens to carries use, move and drop only.

No double use

Using an item removes it before the effect runs, and a per-player lock drops a second request that arrives mid-action, so one potion cannot be used twice.

DataStore saves

Slot numbers, item ids and counts save to the Inventory_v1 DataStore when a player leaves and when the server shuts down. A failed load is never saved over, so a DataStore outage cannot wipe a player's items. On join, items that are no longer defined are skipped.

Files Picoo ships for this prompt

9 files · 1256 lines · one recipe call

ReplicatedStorage/Items

ModuleScript with the slot count and the item definitions (name and stack size) that server and client both read.

24 lines

ServerScriptService/InventoryServer

Per-player inventories, stacking, use/move/drop handling, DataStore load and save, and the InventoryServer.add and .has functions.

274 lines

StarterPlayer/StarterPlayerScripts/InventoryClient

Draws the grid from the server's snapshot, sends slot clicks and toggles the panel with B.

93 lines

StarterGui/InventoryGui

ScreenGui with the panel, title and 20 slot buttons (66 instances).

instance

ReplicatedStorage/Remotes

Folder for the inventory remotes.

instance

ReplicatedStorage/Remotes/InventorySync

Server → client inventory snapshot.

instance

ReplicatedStorage/Remotes/InventoryAction

Client → server use, move and drop requests.

instance

ReplicatedStorage/PicooFeedback

Shared game-feel module behind the item-added popup.

852 lines

StarterPlayer/StarterPlayerScripts/PicooFeedbackClient

Loads the client half of PicooFeedback for every player.

13 lines

Sample output: ServerScriptService/InventoryServer

--- Returns how many were actually added. Callers MUST check: an inventory that
--- silently drops the remainder tells the player they got something they did not.
local function addItem(player: Player, id: string, count: number): number
	local def = Items.get(id)
	if not def or count <= 0 then return 0 end
	local inv = inventory[player.UserId]
	-- After a failed load nothing this session is saved, so on a live server
	-- report 0 granted: a receipt or shop that checks the return value keeps the
	-- purchase unpaid. A Studio play test plays on (nothing is saved there).
	if not inv or not (loaded[player.UserId] or playTest[player.UserId]) then return 0 end

	local remaining = math.floor(count)

	-- Top up existing stacks first, then take empty slots.
	for slot = 1, Items.MAX_SLOTS do
		if remaining <= 0 then break end
		local s = inv[slot]
		if s and s.id == id and s.count < def.stack then
			local room = def.stack - s.count
			local moved = math.min(room, remaining)
			s.count += moved
			remaining -= moved
		end
	end
	for slot = 1, Items.MAX_SLOTS do
		if remaining <= 0 then break end
		if inv[slot] == nil then
			local moved = math.min(def.stack, remaining)
			inv[slot] = { id = id, count = moved }
			remaining -= moved
		end
	end

	local added = math.floor(count) - remaining
	if added > 0 then
		push(player)
		-- Names the item, because "+2" over a closed backpack tells the player
		-- nothing. Only what actually fit is announced.
		Feedback.score(player, added, { suffix = " " .. def.name })
	end
	return added
end

Building a Roblox inventory system

Inventory systems are the backbone of any item-driven Roblox game, and the most common exploit in them looks harmless in code review: a RemoteEvent the client fires to add an item. However carefully it is validated, it is an item generator. Picoo's inventory recipe has no such remote. Items enter an inventory only when server code calls _G.InventoryServer.add.

The recipe writes three scripts of its own. ReplicatedStorage.Items holds the slot count and the item definitions, so the server and the grid never disagree about names or stack sizes. ServerScriptService.InventoryServer owns every inventory. InventoryClient draws whatever snapshot the server sends, one row per filled slot, and stores nothing of its own, which keeps the grid in step with the server.

Stacking tops up existing stacks before it takes an empty slot: picking up 5 health potions while holding 3 gives one stack of 8. add returns how many actually fit, so the calling code decides what happens to the rest, and the popup names only what the player got.

Players press B to open the grid. A click selects a slot, a second click on the same slot uses one item, a click on another slot swaps the two, and a right-click deletes the whole stack. Using an item is where double-spends hide, because two requests in the same frame can both read a count of 1. InventoryServer removes the item before it runs the effect and holds a per-player lock, so the second request is dropped.

Inventories save to the Inventory_v1 DataStore when a player leaves and when the server shuts down, as slot numbers, item ids and counts rather than instances. A failed load is never saved over, so a DataStore outage cannot erase what a player owns, and on a live server add grants that player nothing for the rest of the session. In a Studio play test of an unpublished place (or with Studio API access off), where every load fails, items work but are not saved. On join, any item removed from Items.Defs since the last save is skipped instead of sitting in a slot as an unusable ghost.

See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.

Frequently asked

How do I add a new item?+

Add an entry to Items.Defs in ReplicatedStorage.Items with an id, a name and a stack size. The server and the grid both read that table. You can also list your items in the prompt, and the recipe writes them in.

How do players get items?+

Server code calls _G.InventoryServer.add(player, 'potion', 2) from a pickup, a quest reward or a shop. It returns how many fit, so the caller can leave the rest on the ground or tell the player the inventory is full. If the player's inventory failed to load on a live server, it adds nothing and returns 0, so a shop that checks the return value keeps the purchase unpaid; in a Studio play test it adds the items, but nothing is saved. _G.InventoryServer.has(player, id, count) checks whether a player holds enough of an item.

What happens when a player uses an item?+

The server removes one from the slot, then runs the item's effect. The effect is a marked TODO that prints a line to Output, so write your heal, equip or unlock there, or ask Picoo for it in a follow-up prompt.

How is the inventory saved?+

Each filled slot saves as its slot number, item id and count with SetAsync to the Inventory_v1 DataStore when the player leaves, and for every player when the server shuts down. The exception is a player whose load failed: nothing is saved for them that session, so the empty inventory they were given never overwrites their real items. A failed load or save logs a warning. On join, slots out of range and items no longer in Items.Defs are skipped.

Can I add a hotbar, drag-and-drop, icons or rarity colors?+

Ask for them in a follow-up prompt. The recipe's grid shows item names and counts and moves items with two clicks; a hotbar, drag-and-drop, icons or rarity borders are written on top of the same server functions.

Related Picoo prompts