diff --git a/Commands/InterviewTemplateCommands.cs b/Commands/InterviewTemplateCommands.cs
index 7955d26..fd56970 100644
--- a/Commands/InterviewTemplateCommands.cs
+++ b/Commands/InterviewTemplateCommands.cs
@@ -132,7 +132,13 @@ public class InterviewTemplateCommands
 
             List<string> errors = [];
             List<string> warnings = [];
-            template.interview.Validate(ref errors, ref warnings, "interview", 0, 0);
+            template.interview.Validate(ref errors, ref warnings, "interview", template.definitions, 0, 0);
+            foreach (KeyValuePair<string, InterviewStep> definition in template.definitions)
+            {
+                definition.Value.Validate(ref errors, ref warnings, "definitions." + definition.Key, template.definitions, 0, 0);
+            }
+
+
             if (errors.Count != 0)
             {
                 string errorString = string.Join("```\n```", errors);
diff --git a/Interviews/Interview.cs b/Interviews/Interview.cs
index 3b7d544..e102ceb 100644
--- a/Interviews/Interview.cs
+++ b/Interviews/Interview.cs
@@ -58,17 +58,20 @@ public class ReferencedInterviewStep
         return InterviewStep.GetButtonStyle(buttonStyle);
     }
 
-    public bool TryGetReferencedStep(Interview interview, out InterviewStep step)
+    public bool TryGetReferencedStep(Dictionary<string, InterviewStep> definitions, out InterviewStep step, bool ignoreReferenceParameters = false)
     {
-        if (!interview.definitions.TryGetValue(id, out step))
+        if (!definitions.TryGetValue(id, out step))
         {
-            Logger.Error("Could not find referenced step '" + id + "' in interview for channel '" + interview.channelID + "'");
+            Logger.Error("Could not find referenced step '" + id + "' in interview.");
             return false;
         }
 
-        step.buttonStyle = buttonStyle;
-        step.selectorDescription = selectorDescription;
-        step.afterReferenceStep = afterReferenceStep;
+        if (!ignoreReferenceParameters)
+        {
+            step.buttonStyle = buttonStyle;
+            step.selectorDescription = selectorDescription;
+            step.afterReferenceStep = afterReferenceStep;
+        }
 
         return true;
     }
@@ -242,9 +245,25 @@ public class InterviewStep
         }
     }
 
+    // Gets all steps in the interview tree, including after-reference-steps but not referenced steps
+    private void GetAllSteps(ref List<InterviewStep> allSteps)
+    {
+        allSteps.Add(this);
+        foreach (KeyValuePair<string, InterviewStep> step in steps)
+        {
+            step.Value.GetAllSteps(ref allSteps);
+        }
+
+        foreach (KeyValuePair<string, ReferencedInterviewStep> reference in references)
+        {
+            reference.Value.afterReferenceStep?.GetAllSteps(ref allSteps);
+        }
+    }
+
     public void Validate(ref List<string> errors,
                          ref List<string> warnings,
                          string stepID,
+                         Dictionary<string, InterviewStep> definitions,
                          int summaryFieldCount = 0,
                          int summaryMaxLength = 0,
                          InterviewStep parent = null)
@@ -276,26 +295,61 @@ public class InterviewStep
                 case MessageType.END_WITH_SUMMARY:
                 case MessageType.END_WITHOUT_SUMMARY:
                 case MessageType.ERROR:
+                case MessageType.REFERENCE_END:
                 default:
                     break;
             }
         }
 
-        if (messageType is MessageType.ERROR or MessageType.END_WITH_SUMMARY or MessageType.END_WITHOUT_SUMMARY)
+        // TODO: Add url button here when implemented
+        if (messageType is MessageType.REFERENCE_END)
+        {
+            if (!string.IsNullOrWhiteSpace(message))
+            {
+                warnings.Add("The message parameter on '" + messageType + "' steps have no effect.\n\n> " + stepID + ".message");
+            }
+        }
+        else
+        {
+            if (string.IsNullOrWhiteSpace(message))
+            {
+                errors.Add("'" + messageType + "' steps must have a message parameter.\n\n> " + stepID + ".message");
+            }
+        }
+
+        if (messageType is MessageType.ERROR or MessageType.END_WITH_SUMMARY or MessageType.END_WITHOUT_SUMMARY or MessageType.REFERENCE_END)
         {
             if (steps.Count > 0 || references.Count > 0)
             {
-                warnings.Add("Steps of the type '" + messageType + "' cannot have child steps.\n\n" + stepID + ".message-type");
+                warnings.Add("Steps of the type '" + messageType + "' cannot have child steps.\n\n> " + stepID + ".message-type");
             }
 
             if (!string.IsNullOrWhiteSpace(summaryField))
             {
-                warnings.Add("Steps of the type '" + messageType + "' cannot have summary field names.\n\n" + stepID + ".summary-field");
+                warnings.Add("Steps of the type '" + messageType + "' cannot have summary field names.\n\n> " + stepID + ".summary-field");
             }
         }
         else if (steps.Count == 0 && references.Count == 0)
         {
-            errors.Add("Steps of the type '" + messageType + "' must have at least one child step.\n\n" + stepID + ".message-type");
+            errors.Add("Steps of the type '" + messageType + "' must have at least one child step.\n\n> " + stepID + ".message-type");
+        }
+
+        // TODO: Test this
+        foreach (KeyValuePair<string, ReferencedInterviewStep> reference in references)
+        {
+            if (!reference.Value.TryGetReferencedStep(definitions, out InterviewStep referencedStep, true))
+            {
+                errors.Add("'" + reference.Value.id + "' does not exist in the step definitions.\n\n> " + FormatJSONKey(stepID + ".step-references", reference.Key));
+            }
+            else if (reference.Value.afterReferenceStep == null)
+            {
+                List<InterviewStep> allChildSteps = new List<InterviewStep>();
+                referencedStep.GetAllSteps(ref allChildSteps);
+                if (allChildSteps.Any(s => s.messageType == MessageType.REFERENCE_END))
+                {
+                    errors.Add("The '" + FormatJSONKey(stepID + ".step-references", reference.Key) + "' reference needs an after-reference-step as the '" + reference.Value.id + "' definition contains a REFERENCE_END step.");
+                }
+            }
         }
 
         if (messageType is MessageType.END_WITH_SUMMARY)
@@ -304,52 +358,54 @@ public class InterviewStep
             summaryMaxLength += heading?.Length ?? 0;
             if (summaryFieldCount > 25)
             {
-                errors.Add("A summary cannot contain more than 25 fields, but you have " + summaryFieldCount + " fields in this branch.\n\n" + stepID);
+                errors.Add("A summary cannot contain more than 25 fields, but you have " + summaryFieldCount + " fields in this branch.\n\n> " + stepID);
             }
             else if (summaryMaxLength >= 6000)
             {
                 warnings.Add("A summary cannot contain more than 6000 characters, but this branch may reach " + summaryMaxLength + " characters.\n" +
-                             "Use the \"max-length\" parameter to limit text input field lengths, or shorten other parts of the summary message.\n\n" + stepID);
+                             "Use the \"max-length\" parameter to limit text input field lengths, or shorten other parts of the summary message.\n\n> " + stepID);
             }
         }
 
         if (parent?.messageType is not MessageType.BUTTONS && buttonStyle != null)
         {
-            warnings.Add("Button styles have no effect on child steps of a '" + parent?.messageType + "' step.\n\n" + stepID + ".button-style");
+            warnings.Add("Button styles have no effect on child steps of a '" + parent?.messageType + "' step.\n\n> " + stepID + ".button-style");
         }
 
         if (parent?.messageType is not MessageType.TEXT_SELECTOR && selectorDescription != null)
         {
-            warnings.Add("Selector descriptions have no effect on child steps of a '" + parent?.messageType + "' step.\n\n" + stepID + ".selector-description");
+            warnings.Add("Selector descriptions have no effect on child steps of a '" + parent?.messageType + "' step.\n\n> " + stepID + ".selector-description");
         }
 
         if (messageType is not MessageType.TEXT_SELECTOR && selectorPlaceholder != null)
         {
-            warnings.Add("Selector placeholders have no effect on steps of the type '" + messageType + "'.\n\n" + stepID + ".selector-placeholder");
+            warnings.Add("Selector placeholders have no effect on steps of the type '" + messageType + "'.\n\n> " + stepID + ".selector-placeholder");
         }
 
         if (messageType is not MessageType.TEXT_INPUT && maxLength != null)
         {
-            warnings.Add("Max length has no effect on steps of the type '" + messageType + "'.\n\n" + stepID + ".max-length");
+            warnings.Add("Max length has no effect on steps of the type '" + messageType + "'.\n\n> " + stepID + ".max-length");
         }
 
         if (messageType is not MessageType.TEXT_INPUT && minLength != null)
         {
-            warnings.Add("Min length has no effect on steps of the type '" + messageType + "'.\n\n" + stepID + ".min-length");
+            warnings.Add("Min length has no effect on steps of the type '" + messageType + "'.\n\n> " + stepID + ".min-length");
         }
 
         foreach (KeyValuePair<string, InterviewStep> step in steps)
         {
-            // The JSON schema error messages use this format for the JSON path, so we use it here too.
-            string nextStepID = stepID;
-            nextStepID += step.Key.ContainsAny('.', ' ', '[', ']', '(', ')', '/', '\\')
-                ? ".steps['" + step.Key + "']"
-                : ".steps." + step.Key;
-
-            step.Value.Validate(ref errors, ref warnings, nextStepID, summaryFieldCount, summaryMaxLength, this);
+            step.Value.Validate(ref errors, ref warnings, FormatJSONKey(stepID + ".steps", step.Key), definitions, summaryFieldCount, summaryMaxLength, this);
         }
     }
 
+    private string FormatJSONKey(string parentPath, string key)
+    {
+        // The JSON schema error messages use this format for the JSON path, so we use it in the validation too.
+        return parentPath + (key.ContainsAny('.', ' ', '[', ']', '(', ')', '/', '\\')
+            ? "['" + key + "']"
+            : "." + key);
+    }
+
     public DiscordButtonStyle GetButtonStyle()
     {
         return GetButtonStyle(buttonStyle);
diff --git a/Interviews/Interviewer.cs b/Interviews/Interviewer.cs
index 3b32630..6d091d0 100644
--- a/Interviews/Interviewer.cs
+++ b/Interviews/Interviewer.cs
@@ -341,7 +341,7 @@ public static class Interviewer
             case MessageType.MENTIONABLE_SELECTOR:
                 foreach ((string stepPattern, ReferencedInterviewStep reference) in nextStep.references)
                 {
-                    if (!reference.TryGetReferencedStep(interview, out InterviewStep step))
+                    if (!reference.TryGetReferencedStep(interview.definitions, out InterviewStep step))
                     {
                         if (answerMessage != null)
                         {
diff --git a/Interviews/interview_template.schema.json b/Interviews/interview_template.schema.json
index 343ce82..995e041 100644
--- a/Interviews/interview_template.schema.json
+++ b/Interviews/interview_template.schema.json
@@ -192,7 +192,6 @@
         }
       },
       "required": [
-        "message",
         "message-type"
       ],
       "unevaluatedProperties": false