Quick answer: Add class_name SaveGame to the resource script. Save references the class globally; loads work in editor and exports identically.

A SaveGame Resource (.tres) with attached script works perfectly in the editor. The exported build can’t load the same .tres — ResourceLoader.load returns null. The script reference broke during export.

Path-Based vs Class-Based References

Saved Resources with script attached store the script path in the .tres:

[resource]
script = ExtResource("res://save_game.gd")

If the path moves (uid migration) or the script is stripped from the export, load fails. The fix: use class_name so the reference is by class registration, not path.

The Fix

# save_game.gd
class_name SaveGame
extends Resource

@export var player_level: int = 1
@export var player_name: String = ""
@export var last_room: String = ""

After save, the .tres references the class:

[resource]
script_class = "SaveGame"

Load: Godot looks up SaveGame in the global class table; finds the script via that registration regardless of original path.

Verify Class Registration

var exists = ClassDB.class_exists("SaveGame")
print("SaveGame registered: ", exists)

True in editor and exports. False in exports = your script wasn’t included or class_name is missing.

Loading with CACHE_MODE_REPLACE

var save = ResourceLoader.load("user://save.tres", "", ResourceLoader.CACHE_MODE_REPLACE)

Forces fresh load. Useful after editing the save externally or on app restart where you want a clean state.

Verifying

Save in editor; reload. Exit; reload from disk. Works in editor. Export the project; from the exported build, load the same .tres. Should succeed identically.

“class_name turns path-fragile resource references into stable name-based ones. Always add it to custom Resource scripts.”

Every Resource subclass in your project should have a class_name — future-you doesn’t need to remember which ones did or didn’t when debugging save loads.