Roblox GUI Maker logoRoblox GUI Maker
Unofficial third-party tool · Not affiliated with Roblox Corporation

Roblox Inventory GUI Tutorial: Complete Backpack System with DataStore (2026)

2026-07-21 · Roblox GUI Maker Team

Step 1: Build the slot grid with UIGridLayout

In this step you will lay out the inventory panel and the slot grid. Start with a centered Frame for the inventory panel, toggled by the Tab key. Inside it, a ScrollingFrame holds the item slots, and a UIGridLayout arranges them in a grid with a fixed cell size. Use Scale-based sizing so the grid reflows to a single column on a phone. Each slot is a Frame with a rarity-colored UIStroke border, an ImageLabel for the item icon, and a count badge.

Step 2: Add drag-and-drop with UIDragDetector

Drag-and-drop lets players rearrange items by dragging them between slots. Roblox's UIDragDetector makes this much easier than writing manual mouse-following code. Attach a UIDragDetector to each slot, and on drop, swap the item data between the source and target slots and refresh the grid. The visual drag is handled by Roblox; you only need to sync the data on drop.

Step 3: Equip logic with server sync

Equipping is where client and server meet. When the player clicks Equip on a slot, fire a RemoteEvent to the server with the item ID. The server validates the equip (does the player own this item?), updates the equipped state, and fires back to update the HUD or character. Never let the client decide what is equipped - that is how exploiters equip items they do not own.

StarterGui/InventoryClient (LocalScript)
lua
1-- Inventory GUI - Client Script
2-- Place in: StarterGui/InventoryGui (LocalScript)
3
4local Players = game:GetService("Players")
5local ReplicatedStorage = game:GetService("ReplicatedStorage")
6local UserInputService = game:GetService("UserInputService")
7
8local player = Players.LocalPlayer
9local gui = script.Parent
10local slotGrid = gui:WaitForChild("SlotGrid")
11local equipEvent = ReplicatedStorage:WaitForChild("InventoryEquip")
12local stateEvent = ReplicatedStorage:WaitForChild("InventoryState")
13
14-- Toggle inventory with Tab
15UserInputService.InputBegan:Connect(function(input, processed)
16 if processed then return end
17 if input.KeyCode == Enum.KeyCode.Tab then
18 gui.Enabled = not gui.Enabled
19 end
20end)
21
22-- Wire each slot's Equip button
23local function wireSlot(slot)
24 local equipButton = slot:WaitForChild("EquipButton")
25 equipButton.MouseButton1Click:Connect(function()
26 equipEvent:FireServer(slot:GetAttribute("ItemId"))
27 end)
28end
29
30for _, slot in ipairs(slotGrid:GetChildren()) do
31 if slot:IsA("Frame") then
32 wireSlot(slot)
33 end
34end
35
36-- Refresh grid when server pushes state
37stateEvent.OnClientEvent:Connect(function(state)
38 for _, slot in ipairs(slotGrid:GetChildren()) do
39 if slot:IsA("Frame") then
40 local data = state[slot.Name]
41 slot:WaitForChild("Icon").Visible = data ~= nil
42 slot:WaitForChild("EquippedBadge").Visible = data and data.equipped or false
43 end
44 end
45end)

Step 4: Persist with DataStore

DataStore keeps the inventory across sessions. On join, the server loads the player's saved items and pushes them to the client. On equip or leave, the server saves the current state. Always wrap DataStore calls in pcall so a failed save does not crash the game, and save on PlayerRemoving as a final backup.

ServerScriptService/InventoryServer (Script)
lua
1-- Inventory GUI - Server Script
2-- Place in: ServerScriptService (Script)
3
4local Players = game:GetService("Players")
5local ReplicatedStorage = game:GetService("ReplicatedStorage")
6local DataStoreService = game:GetService("DataStoreService")
7
8local invStore = DataStoreService:GetDataStore("PlayerInventory")
9local equipEvent = ReplicatedStorage:WaitForChild("InventoryEquip")
10local stateEvent = ReplicatedStorage:WaitForChild("InventoryState")
11
12local playerInventories = {}
13
14-- Load inventory on join
15local function loadInventory(player)
16 local success, data = pcall(function()
17 return invStore:GetAsync(player.UserId)
18 end)
19 playerInventories[player.UserId] = (success and data) or {}
20 stateEvent:FireClient(player, playerInventories[player.UserId])
21end
22
23-- Save inventory
24local function saveInventory(player)
25 local data = playerInventories[player.UserId]
26 if not data then return end
27 pcall(function()
28 invStore:SetAsync(player.UserId, data)
29 end)
30end
31
32-- Handle equip requests
33equipEvent.OnServerEvent:Connect(function(player, itemId)
34 local inv = playerInventories[player.UserId]
35 if not inv then return end
36 -- Validate ownership before equipping
37 local owns = inv[itemId] ~= nil
38 if owns then
39 inv[itemId].equipped = not inv[itemId].equipped
40 saveInventory(player)
41 stateEvent:FireClient(player, inv)
42 end
43end)
44
45Players.PlayerAdded:Connect(loadInventory)
46Players.PlayerRemoving:Connect(saveInventory)

Item rarity and borders

Rarity is communicated by the UIStroke border color on each slot: gray for common, green for uncommon, blue for rare, purple for epic, gold for legendary. Store the rarity on the item data and set the slot's UIStroke color accordingly. Players read the border color instantly, so keep the mapping consistent across your whole game.

Frequently asked questions