diff --git a/src/Godot/Exceptions/MissingRequiredExportException.cs b/src/Godot/Exceptions/MissingRequiredExportException.cs
new file mode 100644
index 0000000..36888dd
--- /dev/null
+++ b/src/Godot/Exceptions/MissingRequiredExportException.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+
+namespace Game.Godot.Exceptions;
+
+///
+/// Thrown when a node's [Export]ed Node reference is unassigned.
+///
+public sealed class MissingRequiredExportException(string nodeName, IReadOnlyList missingExports)
+ : Exception(BuildMessage(nodeName, missingExports))
+{
+ public string NodeName { get; } = nodeName;
+ public IReadOnlyList MissingExports { get; } = missingExports;
+
+ private static string BuildMessage(string nodeName, IReadOnlyList missingExports)
+ => $"{nodeName}: missing required exports:\n- {string.Join("\n- ", missingExports)}\nAssign them in the editor Inspector.";
+}
diff --git a/src/Godot/Exceptions/MissingRequiredExportException.cs.uid b/src/Godot/Exceptions/MissingRequiredExportException.cs.uid
new file mode 100644
index 0000000..1fb3a80
--- /dev/null
+++ b/src/Godot/Exceptions/MissingRequiredExportException.cs.uid
@@ -0,0 +1 @@
+uid://diqdaysdyp3jf
diff --git a/src/Godot/Extensions/NodeExportValidationExtensions.cs b/src/Godot/Extensions/NodeExportValidationExtensions.cs
new file mode 100644
index 0000000..ad3286f
--- /dev/null
+++ b/src/Godot/Extensions/NodeExportValidationExtensions.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Game.Godot.Exceptions;
+using Godot;
+
+namespace Game.Godot.Extensions;
+
+///
+/// Validates that a node's [Export] fields/properties pointing to other nodes are actually assigned.
+///
+public static class NodeExportValidationExtensions
+{
+ // Tells which members to include when scanning for [Export] fields/properties
+ private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
+
+ private static void DisableProcessing(Node node)
+ {
+ node.SetProcess(false);
+ node.SetPhysicsProcess(false);
+ node.SetProcessInput(false);
+ node.SetProcessUnhandledInput(false);
+ }
+
+ private static string[] GetMissingExportWarnings(Node node)
+ {
+ Type nodeType = node.GetType();
+
+ IEnumerable missingFields = nodeType
+ .GetFields(MemberFlags)
+ .Where(field => field.GetCustomAttribute() is not null)
+ .Where(field => typeof(Node).IsAssignableFrom(field.FieldType))
+ .Where(field => field.GetValue(node) is null)
+ .Select(field => $"{field.Name} ({field.FieldType.Name}) is not assigned.");
+
+ IEnumerable missingProperties = nodeType
+ .GetProperties(MemberFlags)
+ .Where(property => property.GetCustomAttribute() is not null)
+ .Where(property => typeof(Node).IsAssignableFrom(property.PropertyType))
+ .Where(property => property.GetValue(node) is null)
+ .Select(property => $"{property.Name} ({property.PropertyType.Name}) is not assigned.");
+
+ return missingFields.Concat(missingProperties).ToArray();
+ }
+
+ ///
+ /// Throws if any [Export]ed Node field on this node is unassigned.
+ ///
+ /// Node to check.
+ /// One or more exports are missing.
+ ///
+ ///
+ /// public override void _Ready()
+ /// {
+ /// this.AssertRequiredExports();
+ /// // rest of initialization
+ /// }
+ ///
+ ///
+ public static void AssertRequiredExports(this Node node)
+ {
+ List missing = GetMissingExportWarnings(node).ToList();
+ if (missing.Count == 0)
+ {
+ return;
+ }
+
+ DisableProcessing(node);
+
+ throw new MissingRequiredExportException(node.Name, missing);
+ }
+}
diff --git a/src/Godot/Extensions/NodeExportValidationExtensions.cs.uid b/src/Godot/Extensions/NodeExportValidationExtensions.cs.uid
new file mode 100644
index 0000000..d664e2b
--- /dev/null
+++ b/src/Godot/Extensions/NodeExportValidationExtensions.cs.uid
@@ -0,0 +1 @@
+uid://d3twapljpp1ln