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.