Roblox Studio error

104: Cannot store Array in data store

What 104: Cannot store Array in data store means in Roblox Studio, the three things that cause it with the code that triggers each, and the fix for each one.

Updated 4 November 20264 min readErrorLuauWritten by

In short

A data store saves plain data only: nil, booleans, numbers, strings, buffers and tables made of those. The table you passed to SetAsync or returned from UpdateAsync holds something else, usually an Instance (a Tool, a Part, a Player) or a Roblox type such as Vector3, CFrame or Color3. Array or Dictionary in the message is a kind of table, not the bad value inside it. Save plain values instead, such as the tool's name or the three numbers of a position, and rebuild the objects when the data loads.

On this page
  1. What it means
  2. Causes and fixes
  3. Fix it with RoCode
  4. Find the line
  5. Questions
  6. Sources

What the message means

Output (example)
104: Cannot store Array in data store. Data stores can only accept valid UTF-8 characters.

Other forms of the same message:

  • 104: Cannot store Dictionary in data store. Data stores can only accept valid UTF-8 characters.

Roblox turns your table into JSON text before it saves it. Anything JSON cannot hold stops the save with error code 104. The message only says which kind of table Roblox could not save: Array for a list ({a, b, c}), Dictionary for a table with named keys. It never names the value that broke it, and that value can be several levels deep.

The UTF-8 part of the message matters in only one case: a string that is not valid text, such as bytes packed with string.pack. For Instances and Roblox types, ignore it.

Nothing is saved when this happens, so a player who leaves loses that session. Wrap every save in pcall and test a save in Studio before you ship.

Causes and fixes

Three things cause this error in practice. Each one below has the smallest code that triggers it, then the fix.

The table holds Instances

Saving an inventory as a list of the Tool objects themselves is the most common version. An Instance lives in the running server; there is no way to write it into JSON.

Breaks Script in ServerScriptService named SaveInventory
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local store = DataStoreService:GetDataStore("Inventory")

Players.PlayerRemoving:Connect(function(player)
	local backpack = player:FindFirstChildOfClass("Backpack")
	if not backpack then
		return
	end
	-- a list of Tool objects: 104: Cannot store Array in data store
	store:SetAsync(tostring(player.UserId), backpack:GetChildren())
end)

Save something that names each object, usually its Name, and keep the real tools in ServerStorage. When the data loads, clone the tool with that name back into the backpack.

Fixed Script in ServerScriptService named SaveInventory
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local store = DataStoreService:GetDataStore("Inventory")

Players.PlayerRemoving:Connect(function(player: Player)
	local backpack = player:FindFirstChildOfClass("Backpack")
	if not backpack then
		return
	end
	local toolNames: { string } = {}
	for _, item in backpack:GetChildren() do
		if item:IsA("Tool") then
			table.insert(toolNames, item.Name)
		end
	end
	local ok, err = pcall(function()
		store:SetAsync(tostring(player.UserId), toolNames)
	end)
	if not ok then
		warn(`[SaveInventory] save failed for {player.UserId}: {err}`)
	end
end)

When the player joins, load the list and clone ServerStorage.Tools[name] for each name that still exists. A name for a tool you have since removed is simply skipped.

The table holds a Vector3, CFrame or Color3

Saving where a player built something, or the colour they picked, puts Roblox data types into the table. They look like plain values in scripts, but they are not numbers, strings or tables, so serialization fails. The message then says Dictionary or Array, whichever kind of table they sat in.

Breaks Script in ServerScriptService named SavePlot
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("Plots")

local plot = {
	position = Vector3.new(10, 0, -24),
	color = Color3.fromRGB(255, 170, 0),
}

-- 104: Cannot store Dictionary in data store
store:SetAsync("plot_12345", plot)

Store each type as plain numbers and turn them back when you load: {x, y, z} for a position, the hex text from Color3:ToHex() for a colour. For a CFrame, save its twelve numbers from GetComponents(), or just the position and one angle if that is all your game needs.

Fixed Script in ServerScriptService named SavePlot
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("Plots")

local position = Vector3.new(10, 0, -24)
local color = Color3.fromRGB(255, 170, 0)

local saved = {
	position = { position.X, position.Y, position.Z },
	color = color:ToHex(), -- "FFAA00"
}

local ok, err = pcall(function()
	store:SetAsync("plot_12345", saved)
end)
if not ok then
	warn("[SavePlot] save failed:", err)
end

-- when loading:
local function readPlot(data: { position: { number }, color: string })
	local p = data.position
	return Vector3.new(p[1], p[2], p[3]), Color3.fromHex(data.color)
end
print(readPlot(saved))

A string is not valid UTF-8 text

Data packed into a string with string.pack, or text built byte by byte with string.char, can contain bytes that are not valid UTF-8. JSON only holds valid text, so this is the one case where the end of the message is the real reason.

Breaks Script in ServerScriptService named SaveBuild
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("Builds")

-- three numbers packed into raw bytes, which are not valid text
local packed = string.pack("fff", 10.5, 0, -24.25)

-- 104: Cannot store Array in data store. Data stores can only accept valid UTF-8 characters.
store:SetAsync("build_12345", { packed })

Data stores accept buffer values, so pack binary data into a buffer instead of a string. Or keep the numbers as numbers: a table of numbers is often small enough.

Fixed Script in ServerScriptService named SaveBuild
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("Builds")

local data = buffer.create(12)
buffer.writef32(data, 0, 10.5)
buffer.writef32(data, 4, 0)
buffer.writef32(data, 8, -24.25)

local ok, err = pcall(function()
	store:SetAsync("build_12345", data)
end)
if not ok then
	warn("[SaveBuild] save failed:", err)
end

Fix it in your place

Paste the error, RoCode fixes it in your place

Copy the line from Output and paste it here. RoCode opens the script it names in the place you have open in Studio, works out which of the causes above it is, and edits the code there instead of handing you a snippet to paste.

For example: 104: Cannot store Array in data store. Data stores can only accept valid UTF-8 characters.

RoCode is an AI agent for Roblox Studio: it reads the scripts in the place you have open, finds the line and edits it there, and every change is an undo point. It runs on Windows or macOS with a desktop browser. Free to start, with free credits every day.

How to find the line that causes it

  1. The 104 error is raised by the SetAsync or UpdateAsync call. Click the red line in Output to open that script there.
  2. Just above the save, add print(game:GetService("HttpService"):JSONEncode(yourTable)). Values that come out as null or are missing from the printed text are the ones a data store cannot save. If the print line itself errors, the table holds something JSON cannot hold at all.
  3. Walk through the table and print typeof(value) for every value. Anything that prints Instance, Vector3, CFrame, Color3, EnumItem or function is what the data store refuses.

Questions

Can a Roblox data store save a table?

Yes, as long as everything in it is a boolean, a number, a string, a buffer or another such table. Tables inside tables are fine. What fails is an Instance or a Roblox type such as Vector3 anywhere inside it.

How do I save a Vector3 or CFrame in a data store?

Save its numbers. A Vector3 becomes {x, y, z}; a CFrame becomes the twelve numbers from GetComponents(), which CFrame.new takes back in the same order. Turn them back into the type when the data loads.

How do I save a player's tools or items?

Save the names (or your own item IDs) in a list. Keep one copy of each tool in ServerStorage and clone it into the backpack when the player joins and their data loads.

Why does it say Array when my table has keys?

The table that failed can be one inside yours, such as a list of items inside a player's record. Check every level of the table, not just the top.

Can RoCode fix it?

Yes. Paste the Output line into RoCode. It reads the save script in the place you have open, finds the Instance or Roblox type in the saved table and rewrites the save and the load to use plain values.

Sources and further reading

How the code was checked: every block marked Fixed passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 4 November 2026), and every block marked Breaks compiles and fails only when it runs. The message wording was checked against the message strings in Roblox Studio itself (macOS build installed on 24 October 2026) on 4 November 2026. The list of values a data store accepts is from the Serialization section of the Roblox data store docs. A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

More Roblox errors

All Roblox Studio errors

Stop hunting errors line by line.

RoCode reads the scripts in your open place, fixes the error where it happens and tells you what it changed. Free every day, no card required.