Roblox Studio error

attempt to index nil with 'X'

What attempt to index nil with 'X' means in Roblox Studio, the four things that cause it with the code that triggers each, and the fix for each one.

Updated 23 October 20265 min readErrorLuau

In short

Indexing means reading something with a dot or brackets, like player.Character or list[1]. The error says the thing on the left of the dot was nil, and the quoted name is what you tried to read from it. So attempt to index nil with 'Humanoid' on character.Humanoid means character was nil. Find out why that value is missing, and check it with if before you use it.

  • The quoted name is the thing you asked for, not the thing that was missing. The missing thing is just before it.
  • The four usual causes: FindFirstChild found nothing, Players.LocalPlayer used in a server Script, the character had not spawned yet, or a table entry was never set.
  • Do not hide it with pcall. Check the value, and return early when it is missing.
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)
ServerScriptService.CoinPad:9: attempt to index nil with 'leaderstats'

Other forms of the same message:

  • attempt to index nil with 'Humanoid'
  • attempt to index nil with 'Character'
  • attempt to index nil with number

Luau reads a.b.c one step at a time. If a.b is nil, the next step has nothing to read c from, and the script stops with attempt to index nil with 'c'. When the key is a number, as in list[1], the message ends with number instead of a quoted name.

Everything after the failing line in that function stops running. Other scripts keep going, which is why a game can look half broken: the shop opens, but the coins never arrive.

Causes and fixes

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

FindFirstChild found nothing

FindFirstChild returns nil when there is no child with that exact name. A kill brick is the classic case: whatever touches it is assumed to be part of a character, but a dropped tool, a loose part or an accessory has no Humanoid next to it.

Breaks Script in a Part in Workspace named KillBrick
local part = script.Parent

part.Touched:Connect(function(hit)
	-- a loose part has no Humanoid, so FindFirstChild returns nil:
	-- attempt to index nil with 'Health'
	hit.Parent:FindFirstChild("Humanoid").Health = 0
end)

Keep the result in a variable, check it, and only then use it. FindFirstAncestorOfClass("Model") also finds the character when the part that touched is inside an accessory or a held tool.

Fixed Script in a Part in Workspace named KillBrick
local part = script.Parent :: BasePart

part.Touched:Connect(function(hit: BasePart)
	local model = hit:FindFirstAncestorOfClass("Model")
	local humanoid = model and model:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end
end)

Players.LocalPlayer in a server Script

Players.LocalPlayer is the player running that copy of the game, so it exists only in LocalScripts and the ModuleScripts they require. In a server Script it is always nil, and the next dot fails, often as attempt to index nil with 'leaderstats' or with 'Character'.

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

-- On the server LocalPlayer is nil: attempt to index nil with 'leaderstats'
local coins = Players.LocalPlayer.leaderstats.Coins
coins.Value += 10

On the server, use the player that an event gives you: PlayerAdded, Touched (through GetPlayerFromCharacter) or the first argument of OnServerEvent.

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

local pad = workspace:WaitForChild("CoinPad") :: BasePart

pad.Touched:Connect(function(hit: BasePart)
	local character = hit:FindFirstAncestorOfClass("Model")
	local player = character and Players:GetPlayerFromCharacter(character)
	if not player then
		return
	end
	local leaderstats = player:FindFirstChild("leaderstats")
	local coins = leaderstats and leaderstats:FindFirstChild("Coins")
	if coins and coins:IsA("IntValue") then
		coins.Value += 10
	end
end)

This pad pays on every touch. A real coin pad also needs a cooldown per player; the leaderboard guide shows how leaderstats are made.

The character has not spawned yet

player.Character is nil until the character first spawns, and for a moment while it respawns. A LocalScript in StarterPlayerScripts runs before that, so reading player.Character.Humanoid at the top of the script fails with attempt to index nil with 'Humanoid'.

Breaks LocalScript in StarterPlayerScripts named Speed
local Players = game:GetService("Players")

local player = Players.LocalPlayer
-- Character is nil until the first spawn: attempt to index nil with 'Humanoid'
local humanoid = player.Character.Humanoid
humanoid.WalkSpeed = 24

Use the character that exists, or wait for one with player.CharacterAdded:Wait(), and run your code again on every respawn with CharacterAdded.

Fixed LocalScript in StarterPlayerScripts named Speed
local Players = game:GetService("Players")

local player = Players.LocalPlayer

local function onCharacter(character: Model)
	local humanoid = character:WaitForChild("Humanoid", 10)
	if humanoid and humanoid:IsA("Humanoid") then
		humanoid.WalkSpeed = 24
	end
end

if player.Character then
	onCharacter(player.Character)
end
player.CharacterAdded:Connect(onCharacter)

A table entry was never set

Reading a key that is not in a table gives nil, with no error. The error comes one step later, when you read something from that nil. Player data is the usual case: a handler runs before the data for that player has loaded, or after it was removed when they left.

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

local sessionData = {}

Players.PlayerAdded:Connect(function(player)
	task.wait(3) -- stands in for a slow data store load
	sessionData[player] = { Gems = 0 }
end)

local function addGems(player, amount)
	-- before the load finishes: attempt to index nil with 'Gems'
	sessionData[player].Gems += amount
end

Players.PlayerAdded:Connect(function(player)
	addGems(player, 5)
end)

Check that the entry exists before you read from it, and decide what should happen when it does not: skip, queue the change, or wait for the load.

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

type Data = { Gems: number }
local sessionData: { [Player]: Data } = {}

local function addGems(player: Player, amount: number): boolean
	local data = sessionData[player]
	if not data then
		warn(`[Gems] no data for {player.Name} yet`)
		return false
	end
	data.Gems += amount
	return true
end

Players.PlayerAdded:Connect(function(player: Player)
	task.wait(3) -- stands in for a slow data store load
	sessionData[player] = { Gems = 0 }
	addGems(player, 5)
end)

Players.PlayerRemoving:Connect(function(player: Player)
	sessionData[player] = nil
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: ServerScriptService.CoinPad:9: attempt to index nil with 'leaderstats'

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. Open Output (Window menu, or the Script tab), clear it with Ctrl+K (⌘K on Mac), and press Play.
  2. Find the first red line. It starts with the script's place in the Explorer and the line number, such as ServerScriptService.Shop:12:.
  3. Click the red line. Studio opens that script with the cursor on the failing line.
  4. Read the lines between Stack Begin and Stack End under it: they list the calls that led there, newest first, so you can see which function called the broken one.
  5. Still not sure which value is wrong? Add print(typeof(x), x) just above the line, or set a breakpoint on it and read the values in the Watch window.

Questions

What does attempt to index nil with 'Humanoid' mean?

The value just before .Humanoid was nil. In character.Humanoid that is character: usually the character has not spawned, or the part that touched something was not part of a character. Get the character from CharacterAdded or FindFirstAncestorOfClass("Model") and check it first.

Why do I get attempt to index nil with 'leaderstats'?

Most often because a server Script uses Players.LocalPlayer, which is always nil on the server. Use the player from PlayerAdded, GetPlayerFromCharacter or OnServerEvent. If you are already on the client, the leaderstats folder may not have arrived yet: use player:WaitForChild("leaderstats").

Is it different from "X is not a valid member of Y"?

Yes. not a valid member means the object on the left existed but had no child or property with that name. attempt to index nil means the object on the left did not exist at all.

Should I wrap the line in pcall?

No. pcall stops the error message but leaves the bug: the code still does nothing. Check the value with if and handle the missing case on purpose.

Can RoCode fix it for me?

Yes. Paste the Output line into RoCode. It opens the script the error names in your place, finds which value is nil and why, and adds the check or the wait where it belongs.

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, 23 October 2026), and every block marked Breaks compiles and fails only when it runs. The message wording was checked by running the broken code with the Luau command line tool and against the message strings in Roblox Studio itself on 23 October 2026. A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

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.