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.
1-- Inventory GUI - Client Script2-- Place in: StarterGui/InventoryGui (LocalScript)34local Players = game:GetService("Players")5local ReplicatedStorage = game:GetService("ReplicatedStorage")6local UserInputService = game:GetService("UserInputService")78local player = Players.LocalPlayer9local gui = script.Parent10local slotGrid = gui:WaitForChild("SlotGrid")11local equipEvent = ReplicatedStorage:WaitForChild("InventoryEquip")12local stateEvent = ReplicatedStorage:WaitForChild("InventoryState")1314-- Toggle inventory with Tab15UserInputService.InputBegan:Connect(function(input, processed)16 if processed then return end17 if input.KeyCode == Enum.KeyCode.Tab then18 gui.Enabled = not gui.Enabled19 end20end)2122-- Wire each slot's Equip button23local function wireSlot(slot)24 local equipButton = slot:WaitForChild("EquipButton")25 equipButton.MouseButton1Click:Connect(function()26 equipEvent:FireServer(slot:GetAttribute("ItemId"))27 end)28end2930for _, slot in ipairs(slotGrid:GetChildren()) do31 if slot:IsA("Frame") then32 wireSlot(slot)33 end34end3536-- Refresh grid when server pushes state37stateEvent.OnClientEvent:Connect(function(state)38 for _, slot in ipairs(slotGrid:GetChildren()) do39 if slot:IsA("Frame") then40 local data = state[slot.Name]41 slot:WaitForChild("Icon").Visible = data ~= nil42 slot:WaitForChild("EquippedBadge").Visible = data and data.equipped or false43 end44 end45end)
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.
1-- Inventory GUI - Server Script2-- Place in: ServerScriptService (Script)34local Players = game:GetService("Players")5local ReplicatedStorage = game:GetService("ReplicatedStorage")6local DataStoreService = game:GetService("DataStoreService")78local invStore = DataStoreService:GetDataStore("PlayerInventory")9local equipEvent = ReplicatedStorage:WaitForChild("InventoryEquip")10local stateEvent = ReplicatedStorage:WaitForChild("InventoryState")1112local playerInventories = {}1314-- Load inventory on join15local 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])21end2223-- Save inventory24local function saveInventory(player)25 local data = playerInventories[player.UserId]26 if not data then return end27 pcall(function()28 invStore:SetAsync(player.UserId, data)29 end)30end3132-- Handle equip requests33equipEvent.OnServerEvent:Connect(function(player, itemId)34 local inv = playerInventories[player.UserId]35 if not inv then return end36 -- Validate ownership before equipping37 local owns = inv[itemId] ~= nil38 if owns then39 inv[itemId].equipped = not inv[itemId].equipped40 saveInventory(player)41 stateEvent:FireClient(player, inv)42 end43end)4445Players.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.