The problem: you rename a node, move it in the scene tree, or restructure a hierarchy, and any @export NodePath that referenced it silently becomes a broken path. Godot doesn't warn you at edit time. You find out when you hit play and something crashes, or worse, fails silently because you have a null check there and forgot why it exists.
I got burned by this enough times that I finally wrote an EditorPlugin to scan the active scene and report broken paths before they cause problems. Here's the core validator:
@tool
extends EditorPlugin
const DOCK_SCENE = preload("res://addons/path_validator/dock.tscn")
var _dock: Control
func _enter_tree() -> void:
_dock = DOCK_SCENE.instantiate()
add_control_to_dock(DOCK_SLOT_LEFT_BR, _dock)
_dock.validate_requested.connect(_run_validation)
func _exit_tree() -> void:
remove_control_from_docks(_dock)
_dock.queue_free()
func _run_validation() -> void:
var root = get_editor_interface().get_edited_scene_root()
if not root:
return
_dock.display_issues(_scan_node(root))
func _scan_node(node: Node) -> Array[Dictionary]:
var issues: Array[Dictionary] = []
for prop in node.get_property_list():
if not (prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
continue
var val = node.get(prop.name)
if val is NodePath and not val.is_empty():
if node.get_node_or_null(val) == null:
issues.append({
"node": node,
"prop": prop.name,
"issue": "broken_path",
"detail": str(val)
})
elif prop.hint == PROPERTY_HINT_NODE_TYPE and val == null:
issues.append({
"node": node,
"prop": prop.name,
"issue": "null_node_export"
})
for child in node.get_children():
issues.append_array(_scan_node(child))
return issues
The dock is a basic VBoxContainer: scan button, ItemList showing flagged nodes, click to select the offending node in the scene tree. About 80 more lines of dock wiring I won't paste in full.
The part I haven't solved cleanly: false positives. Some null exports are intentional, like optional references the script handles gracefully. Right now everything unset gets flagged regardless. I've been thinking about a naming convention like an _opt suffix to mark optional exports, or storing custom metadata on the property, but both feel like scope creep for a utility tool.
Anyone done something similar? Is there a cleaner pattern for distinguishing "intentionally null" from "this definitely should have been set"?