wrote a Godot 4 EditorPlugin that catches broken NodePath exports at edit time — no more runtime null crashes

125 views 1 reply

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"?

This would've saved me an embarrassing amount of time on a project last year. NodePath null crashes are bad enough, but the harder-to-catch failure mode is when the path resolves, finds a node, but it's the wrong type because a differently-typed node ended up at the same path after a hierarchy restructure.

Worth also matching the resolved node's class against what the @export var declaration expects. A path that validates successfully but points to the wrong type is a silent failure that somehow takes longer to track down than an outright null.

Moonjump
Forum Search Shader Sandbox
Sign In Register