Skip to content

Why is my node null?

Godot 4.x

You’ll see something like one of these in the Debugger panel. The node name and path will be your own:

@implicit_ready(): Node not found: "ScoreLabel" (relative to "/root/Main").
_ready(): Invalid access to property or key 'text' on a base object of type 'null instance'.
_ready(): Cannot call method 'show' on a null value.
What’s happeningGo to
The error includes Node not foundThe path is wrong
The variable is declared at the top of the script with $Missing @onready
It breaks right after you instantiate() a sceneUsed before add_child
It works at first, then breaks during the gameThe node was freed
The variable uses @exportExport not assigned

All the fixes below use this scene. The script is on Main.

  • MainNode2Dhas scriptscript attached here
    • PlayerCharacterBody2D
    • UICanvasLayer
      • ScoreLabelLabelthe node we want

$ is a shortcut for get_node(). These two lines do exactly the same thing, so every fix on this page works for both:

$UI/ScoreLabel
get_node("UI/ScoreLabel")

UI/ScoreLabel is a path relative to the node the script is on. It breaks if:

  • the name doesn’t match exactly. Names are case-sensitive, so $UI/scorelabel fails.
  • you renamed the node, or moved it to a different parent.
  • the script is on a different node than you think.

Fix: let Godot write the path for you. Hold Ctrl and drag the node from the Scene dock into the script editor. Godot writes the whole line with the correct path:

main.gd
extends Node2D
@onready var score_label: Label = $UI/ScoreLabel
func _ready() -> void:
score_label.text = "Score: 0"

Right-click ScoreLabel in the Scene dock → Access as Unique Name. A % icon appears next to it. Now you can find it by name alone, wherever it is in the scene:

  • MainNode2Dhas script
    • PlayerCharacterBody2D
    • UICanvasLayer
      • TopBarHBoxContainer
        • ScoreLabelLabelunique namemoved into TopBar, still found
main.gd
@onready var score_label: Label = $UI/ScoreLabel
@onready var score_label: Label = %ScoreLabel

Moving ScoreLabel into TopBar broke $UI/ScoreLabel, but %ScoreLabel still works. With get_node(), put the % inside the quotes: get_node("%ScoreLabel").

Variables at the top of a script are set before the node’s children exist. Without @onready, $UI/ScoreLabel runs too early and returns null:

var score_label: Label = $UI/ScoreLabel
@onready var score_label: Label = $UI/ScoreLabel

@onready waits until _ready(), when every child is in place.

A scene you instantiate() isn’t in the game until you call add_child(). Until then its _ready() hasn’t run, so all its @onready variables are still null:

spawner.gd
const ENEMY := preload("res://enemy.tscn")
func spawn() -> void:
var enemy := ENEMY.instantiate()
enemy.set_health(50)
add_child(enemy)
enemy.set_health(50)

Fix: call add_child() first, then set things up.

After queue_free(), any variable still pointing at that node is broken. The error mentions a previously freed object. Check before using it:

if is_instance_valid(target):
target.take_damage(10)

@export var target: Node2D is null until you set it in the Inspector. Select the node and drag the target into the field. To catch it early, add an assert. The game then stops with a clear message instead of a confusing error later:

@export var target: Node2D
func _ready() -> void:
assert(target != null, "Assign 'target' in the Inspector")

The end of the error tells you where Godot started looking: (relative to "/root/Main"). Run the game, then click Remote at the top of the Scene dock. That shows the tree as it really is while the game runs, so you can find your node and compare its actual name and path with what your code says.

If it’s still not obvious, add print_tree_pretty() to _ready() and read the tree in the Output panel.