Quick answer: preload runs at parse time and accepts only string literals starting with res:// or uid://. If the path is wrong, the resource is uninported, or you used a variable, preload returns null. For dynamic paths use load() at runtime; for static references prefer uid:// so renames do not break things.
Here is how to fix Godot preload calls that return null for resources sitting deep inside your project tree. The exact same path works in load(), but preload with the same string returns nothing. Or the editor accepts the call but at runtime you get a null reference. Preload has stricter rules than load, and they are easy to violate without realizing it.
The Symptom
You write const SCENE = preload("res://entities/enemies/goblin/goblin.tscn"). The editor underlines nothing. At runtime, instantiating SCENE produces a null reference. Or the script fails to load at all with a parse error like Could not preload resource file.
Replacing preload with load at runtime works fine. The path is correct.
What Causes This
Resource not yet imported. Godot imports resources on demand. A new .tres just added to your project may not have a .import file yet. Preload runs before this can happen and returns null.
Variable in path. preload only accepts string literals. preload(some_variable) is a parse error or returns null silently in some Godot 4 versions.
File on disk but not in Godot’s database. Files copied into the project folder while the editor is open must be re-scanned. Press Project → Reload Current Project or wait for the editor to detect them.
Path case mismatch. On case-insensitive file systems (macOS default, Windows) the OS finds the file with any case, but Godot’s resource database is case-sensitive. res://Enemies/goblin.tscn and res://enemies/goblin.tscn are different paths to Godot.
The Fix
Step 1: Use string literal paths.
# OK
const GOBLIN_SCENE = preload("res://entities/enemies/goblin/goblin.tscn")
# Not OK - variable
var path = "res://entities/enemies/goblin/goblin.tscn"
var scene = preload(path) # Returns null or parse error
# For dynamic paths, use load() instead
var dynamic_scene = load(path)
Step 2: Reload the project after adding files externally. If you copied resources via the file system rather than dragging them into the editor, Godot may not have indexed them. Project → Reload Current Project rebuilds the resource database.
Step 3: Use uid:// for stability. Right-click the file in the FileSystem dock and choose Copy UID. Use that as your preload path. UIDs survive renames and moves, so refactoring will not break references.
const GOBLIN_SCENE = preload("uid://b87dx2nzaq8m4")
Step 4: Verify the file imports correctly. Select the resource in FileSystem. The Inspector shows import settings. If the file shows an error, fix the import (re-import via right-click) before relying on preload.
Step 5: Match path case exactly. Open the file in the editor and look at its path in the bottom status bar. Copy that exact case into your preload string.
Preload vs Load Cheat Sheet
# preload
# Time: Parse time
# Path: String literal only
# Speed: Faster (resource baked in)
# Use for: Static references known at write time
# load
# Time: Runtime
# Path: Any string expression
# Speed: Slower first call, cached after
# Use for: Dynamic paths, conditional loading
Cyclic Preload Pitfall
If scene_a.gd preloads scene_b.gd and scene_b.gd preloads scene_a.gd, the parser cannot resolve either. One side will return null. Break the cycle by using load() in one direction or by extracting shared logic into a separate file.
“preload is parse-time. It needs a literal, an imported file, and a stable path. Three rules, no exceptions.”
Related Issues
For autoload visibility problems, see Autoload Not Accessible. For signal disconnection on reload, see Signal Connection Lost After Scene Reload.
Literal strings. UID paths. Reload after external changes. preload finds it.