Quick answer: Add a class_name declaration to your custom Resource script. Without it, Godot cannot properly serialize the resource type, and exported properties revert to defaults when the scene or resource file is reloaded.

Here is how to fix custom Resource exported properties not saving in Godot. You created a Resource script with @export variables, assigned values in the Inspector, saved the scene, and when you reloaded the project, all your custom values were gone. The properties reset to their defaults as if you never touched them. No error, no warning — just silent data loss.

The Symptom

You define a custom Resource with exported properties. You create an instance of it (either inline on a node or as a standalone .tres file), set values in the Inspector, and save. When you close and reopen the project — or sometimes even when you switch scenes and come back — the values are at their defaults. The resource appears to save (no error), but the data does not persist.

In some cases, properties save correctly in a .tres file but not when the resource is embedded inline in a scene file. In other cases, simple types like int and String save but complex types like arrays or nested resources do not.

What Causes This

1. Missing class_name. Without a class_name registration, Godot saves the resource as a generic Resource type. When it loads the file back, it creates a base Resource instance that has no knowledge of your custom exported properties. The data may be in the file but has no target property to load into.

2. Script path changed. If you move or rename the .gd script file after creating resources, existing .tres files reference the old path. Godot cannot find the script, falls back to a base Resource, and your custom properties vanish.

3. Sub-resources not saved. When a custom Resource contains another Resource as a property, the sub-resource must also be saved. If the sub-resource is created inline (not saved to its own file), it gets embedded in the parent file. But if the parent is a scene file and the sub-resource is not properly flagged, it may be lost on save.

4. Shared reference confusion. Resources are shared by reference. If two nodes reference the same resource and one modifies it at runtime, the change affects both. If you expected per-node data, you need resource_local_to_scene or explicit duplication.

The Fix

Step 1: Add class_name to your Resource script.

# item_data.gd
class_name ItemData
extends Resource

@export var item_name: String = ""
@export var damage: int = 0
@export var icon: Texture2D
@export var tags: Array[String] = []

Step 2: Save as a .tres file. For resources you want to reuse across scenes, save them as standalone files rather than inline. Right-click the resource in the Inspector and choose “Save As.” Use the .tres format for debuggability:

# Create and save a resource in code
func create_item():
  var item = ItemData.new()
  item.item_name = "Iron Sword"
  item.damage = 25
  item.tags = ["weapon", "melee"]

  var err = ResourceSaver.save(item, "res://data/iron_sword.tres")
  if err != OK:
    print("Failed to save resource: %s" % err)

Step 3: Handle sub-resources. If your resource contains nested resources, ensure they are either saved as separate files or properly serialized inline:

# weapon_data.gd
class_name WeaponData
extends Resource

@export var base_stats: ItemData  # Sub-resource
@export var enchantments: Array[ItemData] = []

# When duplicating, deep-copy sub-resources
func duplicate_deep() -> WeaponData:
  var copy = self.duplicate()
  copy.base_stats = base_stats.duplicate() if base_stats else null
  return copy

Step 4: Use resource_local_to_scene for per-instance data.

# In the Inspector, check "Resource > Local to Scene" on the resource
# Or set it in code:
func _ready():
  var my_data = item_data.duplicate()
  my_data.resource_local_to_scene = true

“class_name is not optional for custom resources. It is the serialization key that tells Godot what type to create when loading the file. Without it, your data saves but loads into a generic Resource that discards everything.”

Why This Works

Godot’s resource serialization writes both the data and the type information to the file. The type is identified by the script path and the class_name. When loading, Godot reads the type, finds the script, creates an instance of that class, and then populates its properties from the saved data. Without class_name, the type resolution is fragile — it depends entirely on the script path. If the path changes or the editor cannot resolve the anonymous type, it falls back to a base Resource with no custom properties, silently dropping all your data.

Related Issues

If your resource saves correctly but loads with wrong values at runtime, you may have a shared-reference issue. Two nodes editing the same resource instance at runtime will see each other’s changes. Use duplicate() in _ready() to create independent copies.

For saving player data to disk at runtime (save games), use ResourceSaver.save() with user:// paths rather than res:// which is read-only in exports.

class_name is not decoration. It is how Godot knows what your resource is when it loads the file.