Quick answer: Use UIDs (load("uid://abc123")) for stable references that survive moves and exports. Or load by the imported path (res://art/icon.png works for textures because the import system aliases it). Add the asset to the export Filters explicitly if it’s not referenced by the scene tree.

In editor, load("res://art/icon.png") returns a Texture2D. Build and run the export — load returns null. The asset is in the project, it imports cleanly, but at runtime it’s gone. Two paths fix it; one of them is the right one.

The Symptom

load() works in editor playtest but returns null in export builds. Or a scene’s preloaded resource is fine but a runtime-loaded one breaks. No error is raised; the resource is simply not found.

What Causes This

Godot strips source files (.png, .wav, .glb) from exports. The PCK contains only converted resources (.ctex, .ogg, .scn). Loading by source path works in editor because the import system has the source in the project, but in export the source is gone.

Additionally, the export Filter includes only files that are referenced by scene tree or in the include filter. An asset loaded purely at runtime via a string path is not statically known to the exporter and may be stripped.

The Fix

Pattern 1: Load by UID. Right-click the asset in FileSystem → Copy UID. Use the resulting uid://... string.

var tex := load("uid://b73h9wkdex2c") as Texture2D

UIDs are stable across renames and re-imports. They’re also tracked by the export system, so the asset is included automatically.

Pattern 2: Add to export Filter. Project → Export → Resources tab → Filters to export non-resource files: list explicit paths or wildcards (e.g. art/*.png). For resources that are runtime-loaded, this guarantees they ship.

Pattern 3: Statically reference somewhere. Add the asset as an @export var field on a script in your scene. The exporter follows the scene tree and includes anything statically referenced.

extends Node

@export var spawn_icons: Array[Texture2D] = []   # filled in editor

func get_icon(name: String) -> Texture2D:
    for t in spawn_icons:
        if t.resource_path.contains(name):
            return t
    return null

Async Loads

For large textures or audio, prefer threaded loads:

ResourceLoader.load_threaded_request("uid://abc")
while ResourceLoader.load_threaded_get_status("uid://abc") == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
    await get_tree().process_frame
var tex := ResourceLoader.load_threaded_get("uid://abc")

Diagnosing in an Export

Open the .pck with the Godot editor (drag the file into a fresh project) or use the godot-pck-tool. List contents; confirm your asset is present. If absent, the export filter excluded it.

Verifying

Run the export build with --verbose and grep for “load_threaded_request” or “load_failed.” The console prints which paths failed and why.

“UIDs over paths. Static references where possible. Filter the export. Loads succeed in builds.”

Related Issues

For Godot export template missing, see export template. For preload vs load, see preload circular.

UID. Filter. Static ref. The asset ships.