Generated banner for N8n Validation Expert
Generated logo for N8n Validation Expert

N8n Validation Expert

Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski

  • Skill
  • Tool integration
  • Automation and integrations
  • MIT
  • No issues found

From the author

Interpret validation errors and guide fixing them. Use when encountering validation errors, validation warnings, false positives, operator structure issues, or need help understanding validation results. Also use when asking about validation profiles, error types, the validation loop process, or auto-fix capabilities. Consult this skill whenever a validate_node or validate_workflow call returns errors or warnings — it knows which warnings are false positives and which errors need real fixes.
— the description by Romuald Czlonkowski

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The n8n-validation-expert skill provides a structured approach to managing workflow validation. It emphasizes an iterative validation loop, where users perform multiple cycles of validation and correction to achieve a stable workflow. The skill covers error severity levels, explains the four validation profiles (minimal, runtime, ai-friendly, strict), and details how to handle common error types, auto-sanitization, and false positives. It also provides strategies for testing workflows and performing comprehensive audits on existing configurations.

What it does

  • Interprets and explains validation errors and warnings returned by n8n.
  • Provides guidance on fixing common issues like missing fields, type mismatches, and broken references.
  • Explains how to use different validation profiles to balance strictness and advisory feedback.
  • Details the auto-sanitization process for operator structures.
  • Offers strategies for testing workflows, including handling triggers and pinned data.
  • Provides a framework for auditing existing workflows for security and best practices.

When to use it

  • When a validate_node or validate_workflow call returns errors or warnings.
  • When encountering unexpected validation results or potential false positives.
  • When hardening a production-critical workflow using strict validation profiles.
  • When troubleshooting workflow execution failures related to configuration.
  • When reviewing an existing workflow for potential improvements or security risks.

Advantages

  • Reduces time spent debugging by providing clear, actionable fix guidance.
  • Helps distinguish between critical errors that block execution and optional best-practice warnings.
  • Standardizes the validation process through an iterative, proven loop.
  • Clarifies the use of validation profiles to suit different development stages.
  • Provides specific strategies for handling complex scenarios like cycles and disconnected nodes.

Usage examples

“I am getting a 'missing_required' error on my Slack node. How do I fix it?”
The agent will explain that a required field is missing, suggest using get_node to identify the specific field, and guide you through providing the necessary value.
“My workflow validates but I am getting a warning about 'best_practice'. Should I fix it?”
The agent will explain that best-practice warnings are optional, help you evaluate the trade-offs based on your specific use case, and advise whether to address it or accept it.
“How do I use the strict profile to harden my production workflow?”
The agent will explain how to configure the validation call to use the 'strict' profile and what additional checks, such as leftover-property detection, will be performed.
“I have a workflow that validates but fails at runtime. What should I do?”
The agent will guide you through testing strategies, such as using n8n_test_workflow with pinned data or direct execution, to isolate the issue.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/czlonkowski/n8n-validation-expert/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip n8n-validation-expert.zip -d .agents/skills

Documentation by Romuald Czlonkowski

n8n Validation Expert

Expert guide for interpreting and fixing n8n validation errors.


Validation Philosophy

Validate early, validate often

Validation is typically iterative:

  • Expect validation feedback loops

  • Usually 2-3 validate → fix cycles

  • Average: 23s thinking about errors, 58s fixing them

Key insight: Validation is an iterative process, not one-shot!


Error Severity Levels

1. Errors (Must Fix)

Blocks workflow execution - Must be resolved before activation

Types:

  • missing_required - Required field not provided

  • invalid_value - Value doesn't match allowed options

  • type_mismatch - Wrong data type (string instead of number)

  • invalid_reference - Referenced node doesn't exist

  • invalid_expression - Expression syntax error

Example:

{
  "type": "missing_required",
  "property": "channel",
  "message": "Channel name is required",
  "fix": "Provide a channel name (lowercase, no spaces, 1-80 characters)"
}
2. Warnings (Should Fix)

Doesn't block execution - Workflow can be activated but may have issues

Types:

  • best_practice - Recommended but not required — surfaces under ai-friendly / strict only

  • deprecated - Using old API/feature — surfaces under every profile

  • security - Hardcoded secrets, unauthenticated webhooks — surfaces under every profile

  • performance - Potential performance issue — advisory, ai-friendly / strict

Example (best-practice — appears under ai-friendly / strict):

{
  "type": "warning",
  "nodeName": "Slack",
  "message": "Slack API can have rate limits and transient failures"
}
3. Suggestions (Optional)

Nice to have - Improvements that could enhance workflow

Types:

  • optimization - Could be more efficient

  • alternative - Better way to achieve same result


The Validation Loop

Pattern from Telemetry

7,841 occurrences of this pattern:

1. Configure node
   ↓
2. validate_node (23 seconds thinking about errors)
   ↓
3. Read error messages carefully
   ↓
4. Fix errors
   ↓
5. validate_node again (58 seconds fixing)
   ↓
6. Repeat until valid (usually 2-3 iterations)
Example
// Iteration 1
let config = {
  resource: "channel",
  operation: "create"
};

const result1 = validate_node({
  nodeType: "nodes-base.slack",
  config,
  profile: "runtime"
});
// → Error: Missing "name"

// ⏱️  23 seconds thinking...

// Iteration 2
config.name = "general";

const result2 = validate_node({
  nodeType: "nodes-base.slack",
  config,
  profile: "runtime"
});
// → Error: Missing "text"

// ⏱️  58 seconds fixing...

// Iteration 3
config.text = "Hello!";

const result3 = validate_node({
  nodeType: "nodes-base.slack",
  config,
  profile: "runtime"
});
// → Valid! ✅

This is normal! Don't be discouraged by multiple iterations.


Validation Profiles

The four profiles are cumulative (n8n-mcp ≥ 2.63.0): each surfaces everything the lower one does, plus more. The dividing line is best-practice advisories — minimal and runtime withhold them; ai-friendly and strict add them. Errors are the same across every profile except that minimal skips a few config-level checks (e.g. enum validation of an explicit operation). Security and deprecation warnings surface under every profile.

minimal

Use when: Quick structural checks while wiring a workflow together.

Surfaces: hard errors that would stop execution (missing required fields, empty code, broken connections). Skips enum checks and all advisories.

Fastest and most permissive.

Use when: Ongoing validation as you build; the everyday profile.

Surfaces: errors (required fields, value types, allowed values, dependencies, broken references) plus security and deprecation warnings. No best-practice advisories.

Balanced — catches everything that breaks, stays quiet about style.

ai-friendly

Use when: You want the best-practice advice before deploying.

Surfaces: everything runtime does, plus best-practice advisories — per-node "without error handling" suggestions, "webhook should always send a response", rate-limit notes, outdated-typeVersion suggestions, cachedResultName and long-chain hints.

Note: ai-friendly is stricter than runtime, not looser. (Older docs described it as reducing false positives — that was true only while profile gating was broken; it is fixed now.)

strict

Use when: Hardening a production-critical workflow.

Surfaces: everything ai-friendly does, plus leftover-property checks ("property 'X' won't be used — not visible with current settings").

Maximum lint. With the false positives fixed at the source, its warnings are advice to weigh, not noise to fight.


Common Error Types

Five core error types, in rough order of frequency:

  • missing_required — a required field isn't provided. Use get_node to see required fields, then add it.

  • invalid_value — value doesn't match allowed options (enums are case-sensitive). Check the error's allowed list or get_node.

  • type_mismatch — wrong data type (string "100" vs number 100). Convert to the expected type.

  • invalid_expression — expression syntax error (missing {{}}, typos). See the n8n Expression Syntax skill.

  • invalid_reference — referenced node doesn't exist (renamed, deleted, or misspelled). Fix the name or cleanStaleConnections.

A sixth class, patchNodeField errors (find-not-found, ambiguous match, invalid/unsafe regex), surfaces when a patchNodeField op fails during n8n_update_partial_workflow — it's strict by design and errors rather than silently continuing.

Every type above has worked examples (broken config → fix) plus the patchNodeField error cases and their fixes in ERROR_CATALOG.md.


Auto-Sanitization System

Automatically normalizes common operator structures on ANY workflow update — n8n_create_workflow, n8n_update_partial_workflow, or any save. Trust it; don't hand-fix these.

What it normalizes on save:

  • Binary operators (equals, notEquals, contains, notContains, greaterThan, lessThan, startsWith, endsWith) — removes a stray singleValue property.

  • Unary operators (isEmpty, isNotEmpty, true, false) — adds singleValue: true.

  • IF/Switch metadata — fills in conditions.options for IF v2.2+ and Switch v3.2+.

Validation no longer errors on these shapes (n8n-mcp ≥ 2.63.0). n8n derives unary-ness from the operator name and defaults the conditions.options sub-fields, so validate_node / validate_workflow accept a condition whether or not singleValue and the options metadata are present — the sanitizer just tidies the canonical form on save. (Older servers wrongly errored on the un-normalized shape; if you see that, upgrade.) What still is a real error: a v1-shaped conditions object on a v2 node, an empty filter with no conditions, and legacy v1 operator names (e.g. smaller) inside a v2 structure.

What the sanitizer CANNOT fix (handle manually): broken connections to non-existent nodes (use cleanStaleConnections), branch-count mismatches (add/remove connections or rules), and paradoxical corrupt states (may need manual DB intervention).

Before/after examples and the full cannot-fix detail are in ERROR_CATALOG.md (Auto-Sanitization sections).


False Positives

The validator overhaul (n8n-mcp ≥ 2.63.0) removed the classic false positives — template literals inside expressions, optional chaining, omitted-operation defaults, the Webhook → Respond-to-Webhook pattern, IF/Filter legacy shapes, and more no longer fire.

Known exceptions (n8n-mcp 2.85.0, reported upstream; re-check after upgrading):

  • ERROR "Incorrect error output configuration… appear to be error handlers but are in main[0]" on a fan-out where one target is a Respond to Webhook or Send Email node, or has error / fail / catch / exception in its name. Moving Respond to Webhook onto main[1], as suggested, means the webhook only answers when the upstream node fails. Treat it as a false positive only when the message matches this text exactly and you've inspected connections and confirmed the named node sits on the success path by design. In that case keep the wiring, say in your reply that you're ignoring n8n-mcp#1111 and why, and don't run n8n_autofix_workflow with the default fix types (exclude error-output-config, or it may rewire the success path). Every other valid: false error still gets fixed. (n8n-mcp#1111)

  • Warning "Possible missing $ prefix" on json/items inside a string, e.g. $jmespath($('X').all(), "[?json.country=='PL'].json.name"). The json. prefix is required there, so ignore the warning. (#1115)

  • validate_node on a language: "pythonNative" Code node → "Code cannot be empty" (jsCode). The error is false; validate the workflow instead. (#1112)

  • Python "Return value must be a list of dicts" for a single-dict return in all-items mode. n8n accepts it and emits one item. (#1113)

Blind spots (valid workflow, wrong result at runtime): $jmespath syntax/quoting mistakes inside expressions (#1114); any JS error inside {{ }}, which resolves to null while the execution stays green (see n8n-expression-syntax); native-Python mistakes such as legacy _input/_json, dot access, blocked imports and classes (#1113, see n8n-code-python). Validation plus a successful run still isn't proof: inspect the output values.

What remains are best-practice advisories (surfaced only under ai-friendly / strict) that flag a real trade-off but may be acceptable in your case. Not every advisory needs a fix — many are context-dependent. Common ones and when each is acceptable vs. worth fixing:

  • "...without error handling" — OK for dev/testing and non-critical notifications; fix for production handling important data. (Never a hard error — style doesn't block execution.)

  • "No retry logic" — OK for idempotent ops, APIs with their own retry, manual triggers; fix for flaky external services and production automation.

  • "...rate limits and transient failures" — OK for internal/low-volume/server-side-limited APIs; fix for public, high-volume APIs.

  • "Unbounded query" — OK for small known datasets, aggregations, dev/testing; fix for production queries on large tables.

Security and deprecation warnings, by contrast, surface under every profile and should be treated as real.

Full per-case guidance, the list of what the validator no longer flags, profile strategies, the "should I fix this?" decision framework, and how to document accepted advisories are in FALSE_POSITIVES.md.


Validation Result Structure

Complete Response
{
  "valid": false,
  "errors": [
    {
      "type": "missing_required",
      "property": "channel",
      "message": "Channel name is required",
      "fix": "Provide a channel name (lowercase, no spaces)"
    }
  ],
  "warnings": [
    {
      "type": "best_practice",
      "property": "errorHandling",
      "message": "Slack API can have rate limits",
      "suggestion": "Add onError: 'continueRegularOutput'"
    }
  ],
  "suggestions": [
    {
      "type": "optimization",
      "message": "Consider using batch operations for multiple messages"
    }
  ],
  "summary": {
    "hasErrors": true,
    "errorCount": 1,
    "warningCount": 1,
    "suggestionCount": 1
  }
}
How to Read It
  1. Check valid first — true means the config is valid; false means there are errors to fix before deployment.

  2. Fix errors first — each carries a property, message, and fix. These must be resolved.

  3. Review warnings — each has a message and suggestion; decide per-case whether to address it (see False Positives above).

  4. Consider suggestions — optional improvements, not required.


Workflow Validation

validate_workflow (Structure)

Validates entire workflow, not just individual nodes

Checks:

  1. Node configurations - Each node valid

  2. Connections - No broken references

  3. Expressions - Syntax and references valid

  4. Flow - Logical workflow structure

Example:

validate_workflow({
  workflow: {
    nodes: [...],
    connections: {...}
  },
  options: {
    validateNodes: true,
    validateConnections: true,
    validateExpressions: true,
    profile: "runtime"
  }
})
Common Workflow Errors
1. Broken Connections
{
  "error": "Connection from 'Transform' to 'NonExistent' - target node not found"
}

Fix: Remove stale connection or create missing node

2. Cycles (warning, not an error)
{
  "warning": "Workflow contains a cycle: Node A → Node B → Node A"
}

A cycle is a warning, not a hard error (n8n-mcp ≥ 2.63.0) — runtime-controlled loops (error-retry, data-driven pagination, a router feeding back) execute to completion and are legitimate. Fix only if the loop is unintentional: ensure the cycle has a real exit (a conditional node, an error output, or a bounded counter) so it can't spin forever.

3. Multiple Start Nodes
{
  "warning": "Multiple trigger nodes found - only one will execute"
}

Fix: Remove extra triggers or split into separate workflows

4. Disconnected Nodes
{
  "warning": "Node 'Transform' is not connected to workflow flow"
}

Fix: Connect node or remove if unused


Recovery Strategies

Strategy 1: Start Fresh

When: Configuration is severely broken

Steps:

  1. Note required fields from get_node

  2. Create minimal valid configuration

  3. Add features incrementally

  4. Validate after each addition

When: Workflow validates but executes incorrectly

Steps:

  1. Remove half the nodes

  2. Validate and test

  3. If works: problem is in removed nodes

  4. If fails: problem is in remaining nodes

  5. Repeat until problem isolated

Strategy 3: Clean Stale Connections

When: "Node not found" errors

Steps:

n8n_update_partial_workflow({
  id: "workflow-id",
  operations: [{
    type: "cleanStaleConnections"
  }]
})
Strategy 4: Use Auto-fix

When: Validation errors that can be automatically resolved

Steps:

// Preview fixes (default - doesn't apply)
n8n_autofix_workflow({
  id: "workflow-id",
  applyFixes: false,
  confidenceThreshold: "medium"  // high, medium, low
})

// Review fixes, then apply
n8n_autofix_workflow({
  id: "workflow-id",
  applyFixes: true
})

Auto-Fix Capabilities

The n8n_autofix_workflow tool can fix these issue types:

  1. expression-format - Missing = prefix in expressions (e.g., {{ $json.field }} → ={{ $json.field }})

  2. typeversion-correction - Downgrades nodes with unsupported typeVersions

  3. error-output-config - Removes conflicting onError settings

  4. node-type-correction - Fixes unknown node types using similarity matching (90%+ confidence)

  5. webhook-missing-path - Generates UUIDs for webhook nodes missing path configuration

  6. typeversion-upgrade - Smart upgrades to latest node versions with auto-migration

  7. version-migration - Guidance for complex breaking changes requiring manual steps

Confidence levels: high (90%+, safe to auto-apply), medium (70-89%, review recommended), low (<70%, manual review required)

// Preview all fixes
n8n_autofix_workflow({id: "workflow-id"})

// Only apply high-confidence fixes
n8n_autofix_workflow({
  id: "workflow-id",
  applyFixes: true,
  confidenceThreshold: "high"
})

// Target specific fix types
n8n_autofix_workflow({
  id: "workflow-id",
  fixTypes: ["expression-format", "typeversion-upgrade"],
  applyFixes: true
})

Post-update guidance: For version upgrades, check the postUpdateGuidance field in the response for step-by-step migration instructions.


Best Practices

✅ Do
  • Validate after every significant change

  • Read error messages completely

  • Fix errors iteratively (one at a time)

  • Use runtime profile for pre-deployment

  • Check valid field before assuming success

  • Trust auto-sanitization for operator issues

  • Use get_node when unclear about requirements

  • Document false positives you accept

❌ Don't
  • Skip validation before activation

  • Try to fix all errors at once

  • Ignore error messages

  • Use strict profile during development (too noisy)

  • Assume validation passed (always check result)

  • Manually fix auto-sanitization issues

  • Deploy with unresolved errors

  • Ignore all warnings (some are important!)


Running the workflow after it validates

validate_workflow checks structure, parameters and expressions — it never runs anything. A workflow that validates cleanly can still fail on real data, so run it once before calling it done.

With a webhook, form or chat trigger: n8n_test_workflow({workflowId}) — the default method: "auto" detects the trigger and fires it over HTTP (the workflow must be active).

Without such a trigger (Manual Trigger, Schedule, sub-workflow) there is no HTTP entry point. Use the pin-data path, which runs through n8n's own MCP server (N8N_MCP_ACCESS_TOKEN, n8n 2.34+):

  1. n8n_test_workflow({workflowId, method: "prepare"}) — lists the nodes that need pinned data.

  2. Build one sample item per listed node, keyed by node name, each item wrapped in {json: {...}}:

    {"When clicking 'Test workflow'": [{"json": {"orderId": "1234", "email": "[email protected]"}}]}

    A flat object instead of an array of {json} items is the usual mistake here.

  3. n8n_test_workflow({workflowId, method: "pinned", pinData}) — runs it with that data and waits for the result.

For a quick manual run without pinned data, method: "direct" starts a manual execution and returns as soon as it has started; poll n8n_executions({action: "get", id: executionId, mode: "error"}) for the outcome. Nothing is pinned on a direct run, so every node executes and any external call it makes is real — and even pinned only pins trigger, credentialed and HTTP Request nodes. executionMode: "production" changes the execution context, not whether there are side effects; leave it at the default unless the user asked for a production run.

method: "auto" never runs a workflow through n8n's MCP server. On a workflow with no external trigger it reports that fact and names prepare/pinned/direct; the routed methods only run when you ask for them by name.

Consent before the first routed run. n8n refuses these calls for a workflow whose "Available in MCP" setting is off, which comes back as WORKFLOW_NOT_EXPOSED. Re-running with exposeToMcp: true turns that setting on and retries once. It is a visible, persistent setting on the workflow (and enabling it is a workflow update, so a concurrent UI edit can be overwritten) — ask the user before passing it. The consent flow only ever enables the setting; nothing disables it implicitly. Turning it off again is a deliberate act: n8n_update_partial_workflow({id: workflowId, operations: [{type: "updateSettings", settings: {availableInMCP: false}}]}), or the toggle in the n8n UI.

Reading the result: a run that started and then failed comes back as EXECUTION_FAILED with the executionId — inspect it with n8n_executions({action: "get", id, mode: "error"}) and fix from the node that threw, then validate and run again.


Reviewing an existing workflow

Validating as you build (the loop above) is for catching schema and shape errors in your own in-progress work. Reviewing an existing workflow — yours or one you've been handed — is a different job: the workflow already passes validate_workflow clean, and you're hunting for the issues validation doesn't see (silent connection bugs, injection-prone queries, dropped-item Switches, Set/Code antipatterns, missing error paths). For that, pull the workflow with n8n_get_workflow and walk REVIEW_CHECKLIST.md — a severity-tiered audit (MUST FIX / SHOULD FIX / NICE TO HAVE) where every item points to the canonical skill for the fix. Run n8n_audit_instance alongside it to surface hardcoded secrets and unauthenticated webhooks across the whole instance.


Detailed Guides

For comprehensive error catalogs, false positives, and workflow review:


Summary

Key Points:

  1. Validation is iterative (avg 2-3 cycles, 23s + 58s)

  2. Errors must be fixed, warnings are optional

  3. Auto-sanitization normalizes operator structures on save; validation no longer errors on the raw shape

  4. Use runtime profile by default; step up to ai-friendly/strict for best-practice advisories

  5. Classic false positives are fixed (≥ 2.63.0) — remaining warnings are advisories or security/deprecation notices, not validator mistakes

  6. Read error messages - they contain fix guidance

Validation Process:

  1. Validate → Read errors → Fix → Validate again

  2. Repeat until valid (usually 2-3 iterations)

  3. Review warnings and decide if acceptable

  4. Deploy with confidence

Related Skills & Tools:

  • n8n MCP Tools Expert - Use validation tools correctly

  • n8n Expression Syntax - Fix expression errors

  • n8n Node Configuration - Understand required fields

  • n8n_audit_instance - Proactive security validation (hardcoded secrets, unauthenticated webhooks, missing error handling, data retention)

Files

ERROR_CATALOG.md

Error Catalog

Comprehensive catalog of n8n validation errors with real examples and fixes.


Error Types Overview

Common validation errors by priority:

Error TypePrioritySeverityAuto-Fix
missing_requiredHighestError❌
invalid_valueHighError❌
type_mismatchMediumError❌
invalid_expressionMediumError❌
invalid_referenceLowError❌
operator_structureLowestNot flagged✅ normalized on save

operator_structure is no longer a validation finding (n8n-mcp ≥ 2.63.0). n8n derives unary/binary operators from the operator name and defaults the metadata, so both the raw and normalized shapes validate clean; the sanitizer just tidies the canonical form on save. See section 9.


Errors (Must Fix)

1. missing_required

What it means: Required field is not provided in node configuration

When it occurs:

  • Creating new nodes without all required fields

  • Copying configurations between different operations

  • Switching operations that have different requirements

Most common validation error. In practice the message reads Required property 'X' cannot be empty (e.g. Required property 'URL' cannot be empty, or Code cannot be empty for an empty Code node) — using the field's display name. This is a genuine error under every profile: n8n's own publish validation rejects these identically. It is not a false positive, even on templates where the field was stripped.

Example 1: Slack Channel Missing

Error:

{
  "type": "missing_required",
  "property": "channel",
  "message": "Channel name is required",
  "node": "Slack",
  "path": "parameters.channel"
}

Broken Configuration:

{
  "resource": "message",
  "operation": "post"
  // Missing: channel
}

Fix:

{
  "resource": "message",
  "operation": "post",
  "channel": "#general"  // ✅ Added required field
}

How to identify required fields:

// Use get_node to see what's required
const info = get_node({
  nodeType: "nodes-base.slack"
});
// Check properties marked as "required": true
Example 2: HTTP Request Missing URL

Error:

{
  "type": "missing_required",
  "property": "url",
  "message": "URL is required for HTTP Request",
  "node": "HTTP Request",
  "path": "parameters.url"
}

Broken Configuration:

{
  "method": "GET",
  "authentication": "none"
  // Missing: url
}

Fix:

{
  "method": "GET",
  "authentication": "none",
  "url": "https://api.example.com/data"  // ✅ Added
}
Example 3: Database Query Missing Connection

Error:

{
  "type": "missing_required",
  "property": "query",
  "message": "SQL query is required",
  "node": "Postgres",
  "path": "parameters.query"
}

Broken Configuration:

{
  "operation": "executeQuery"
  // Missing: query
}

Fix:

{
  "operation": "executeQuery",
  "query": "SELECT * FROM users WHERE active = true"  // ✅ Added
}
Example 4: Conditional Fields

Error:

{
  "type": "missing_required",
  "property": "body",
  "message": "Request body is required when sendBody is true",
  "node": "HTTP Request",
  "path": "parameters.body"
}

Broken Configuration:

{
  "method": "POST",
  "url": "https://api.example.com/create",
  "sendBody": true
  // Missing: body (required when sendBody=true)
}

Fix:

{
  "method": "POST",
  "url": "https://api.example.com/create",
  "sendBody": true,
  "body": {
    "contentType": "json",
    "content": {
      "name": "John",
      "email": "[email protected]"
    }
  }  // ✅ Added conditional required field
}

2. invalid_value

What it means: Provided value doesn't match allowed options or format

When it occurs:

  • Using wrong enum value

  • Typos in operation names

  • Invalid format for specialized fields (emails, URLs, channels)

Second most common error

This fires only for an explicitly wrong value. Omitting operation on a multi-resource node (Gmail, Telegram, Slack, Google Drive, Discord, Notion, …) no longer produces a fabricated "Invalid value for 'operation'" error (n8n-mcp ≥ 2.63.0) — the validator now resolves the correct per-resource default first. Expression values (=...) and dynamically-loaded option lists are also skipped. Under the minimal profile the operation enum check is not run at all.

Example 1: Invalid Operation

Error:

{
  "type": "invalid_value",
  "property": "operation",
  "message": "Operation must be one of: post, update, delete, get",
  "current": "send",
  "allowed": ["post", "update", "delete", "get"]
}

Broken Configuration:

{
  "resource": "message",
  "operation": "send"  // ❌ Invalid - should be "post"
}

Fix:

{
  "resource": "message",
  "operation": "post"  // ✅ Use valid operation
}
Example 2: Invalid HTTP Method

Error:

{
  "type": "invalid_value",
  "property": "method",
  "message": "Method must be one of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS",
  "current": "FETCH",
  "allowed": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
}

Broken Configuration:

{
  "method": "FETCH",  // ❌ Invalid
  "url": "https://api.example.com"
}

Fix:

{
  "method": "GET",  // ✅ Use valid HTTP method
  "url": "https://api.example.com"
}
Example 3: Invalid Channel Format

Error:

{
  "type": "invalid_value",
  "property": "channel",
  "message": "Channel name must start with # and be lowercase (e.g., #general)",
  "current": "General"
}

Broken Configuration:

{
  "resource": "message",
  "operation": "post",
  "channel": "General"  // ❌ Wrong format
}

Fix:

{
  "resource": "message",
  "operation": "post",
  "channel": "#general"  // ✅ Correct format
}
Example 4: Invalid Enum with Case Sensitivity

Error:

{
  "type": "invalid_value",
  "property": "resource",
  "message": "Resource must be one of: channel, message, user, file",
  "current": "Message",
  "allowed": ["channel", "message", "user", "file"]
}

Note: Enums are case-sensitive!

Broken Configuration:

{
  "resource": "Message",  // ❌ Capital M
  "operation": "post"
}

Fix:

{
  "resource": "message",  // ✅ Lowercase
  "operation": "post"
}

3. type_mismatch

What it means: Value is wrong data type (string instead of number, etc.)

When it occurs:

  • Hardcoding values that should be numbers

  • Using expressions where literals are expected

  • JSON serialization issues

Common error

Example 1: String Instead of Number

Error:

{
  "type": "type_mismatch",
  "property": "limit",
  "message": "Expected number, got string",
  "expected": "number",
  "current": "100"
}

Broken Configuration:

{
  "operation": "executeQuery",
  "query": "SELECT * FROM users",
  "limit": "100"  // ❌ String
}

Fix:

{
  "operation": "executeQuery",
  "query": "SELECT * FROM users",
  "limit": 100  // ✅ Number
}
Example 2: Number Instead of String

Error:

{
  "type": "type_mismatch",
  "property": "channel",
  "message": "Expected string, got number",
  "expected": "string",
  "current": 12345
}

Broken Configuration:

{
  "resource": "message",
  "operation": "post",
  "channel": 12345  // ❌ Number (even if channel ID)
}

Fix:

{
  "resource": "message",
  "operation": "post",
  "channel": "#general"  // ✅ String (channel name, not ID)
}
Example 3: Boolean as String

Error:

{
  "type": "type_mismatch",
  "property": "sendHeaders",
  "message": "Expected boolean, got string",
  "expected": "boolean",
  "current": "true"
}

Broken Configuration:

{
  "method": "GET",
  "url": "https://api.example.com",
  "sendHeaders": "true"  // ❌ String "true"
}

Fix:

{
  "method": "GET",
  "url": "https://api.example.com",
  "sendHeaders": true  // ✅ Boolean true
}
Example 4: Object Instead of Array

Error:

{
  "type": "type_mismatch",
  "property": "tags",
  "message": "Expected array, got object",
  "expected": "array",
  "current": {"tag": "important"}
}

Broken Configuration:

{
  "name": "New Channel",
  "tags": {"tag": "important"}  // ❌ Object
}

Fix:

{
  "name": "New Channel",
  "tags": ["important", "alerts"]  // ✅ Array
}

4. invalid_expression

What it means: n8n expression has syntax errors or invalid references

When it occurs:

  • Missing {{}} around expressions

  • Typos in variable names

  • Referencing non-existent nodes or fields

  • Invalid JavaScript syntax in expressions

Moderately common

Related: See n8n Expression Syntax skill for comprehensive expression guidance

Static validation catches expression format, not resolution. validate_node / validate_workflow flag a missing = prefix (error) and a bare unwrapped $json reference (warning). They do not resolve node names or property paths inside an expression — a typo'd $('Node') reference or a missing property (Examples 2–4 below) surfaces at execution time, not during validation. Backtick template literals with ${...} interpolation inside {{ }} are valid and are not flagged (n8n-mcp ≥ 2.63.0).

Example 1: Missing = Prefix

An expression field whose value contains {{ }} but doesn't start with = is treated as literal text — this is a real, static error.

Error:

{
  "type": "error",
  "property": "assignments.assignments[0].value",
  "message": "Expression requires = prefix to be evaluated",
  "current": "{{ $json.name }}"
}

Broken Configuration:

{
  "text": "{{ $json.name }}"  // ❌ Has braces but no leading =
}

Fix:

{
  "text": "={{ $json.name }}"  // ✅ Leading = makes it an expression
}

A bare reference with no braces at all — "$json.name" — is a warning, not an error ("possible unwrapped expression"); n8n treats it as literal text, so wrap it as ={{ $json.name }} if you meant to evaluate it. n8n_autofix_workflow can add the missing = for you.

Example 2: Invalid Node Reference

Error:

{
  "type": "invalid_expression",
  "property": "value",
  "message": "Referenced node 'HTTP Requets' does not exist",
  "current": "={{$node['HTTP Requets'].json.data}}"
}

Broken Configuration:

{
  "field": "data",
  "value": "={{$node['HTTP Requets'].json.data}}"  // ❌ Typo in node name
}

Fix:

{
  "field": "data",
  "value": "={{$node['HTTP Request'].json.data}}"  // ✅ Correct node name
}
Example 3: Invalid Property Access

Error:

{
  "type": "invalid_expression",
  "property": "text",
  "message": "Cannot access property 'user' of undefined",
  "current": "={{$json.data.user.name}}"
}

Broken Configuration:

{
  "text": "={{$json.data.user.name}}"  // ❌ Structure doesn't exist
}

Fix (with safe navigation):

{
  "text": "={{$json.data?.user?.name || 'Unknown'}}"  // ✅ Safe navigation + fallback
}
Example 4: Webhook Data Access Error

Error:

{
  "type": "invalid_expression",
  "property": "value",
  "message": "Property 'email' not found in $json",
  "current": "={{$json.email}}"
}

Common Gotcha: Webhook data is under .body!

Broken Configuration:

{
  "field": "email",
  "value": "={{$json.email}}"  // ❌ Missing .body
}

Fix:

{
  "field": "email",
  "value": "={{$json.body.email}}"  // ✅ Webhook data under .body
}

5. invalid_reference

What it means: Configuration references a node that doesn't exist in the workflow

When it occurs:

  • Node was renamed or deleted

  • Typo in node name

  • Copy-pasting from another workflow

Less common error

Which of these validation actually catches: a broken connection to a missing node (Example 2) is a hard error under every profile — Connection to non-existent node: "X" from "Y". A node reference inside an expression (Examples 1 & 3, e.g. $('Old Name')) is not statically resolved — it fails at execution, not during validation. Fix expression references by hand or with the n8n Expression Syntax skill; fix connection references with cleanStaleConnections.

Example 1: Deleted Node Reference

Error:

{
  "type": "invalid_reference",
  "property": "expression",
  "message": "Node 'Transform Data' does not exist in workflow",
  "referenced_node": "Transform Data"
}

Broken Configuration:

{
  "value": "={{$node['Transform Data'].json.result}}"  // ❌ Node deleted
}

Fix:

// Option 1: Update to existing node
{
  "value": "={{$node['Set'].json.result}}"
}

// Option 2: Remove expression if not needed
{
  "value": "default_value"
}
Example 2: Connection to Non-Existent Node

Error:

{
  "type": "invalid_reference",
  "message": "Connection references node 'Slack1' which does not exist",
  "source": "HTTP Request",
  "target": "Slack1"
}

Fix: Use cleanStaleConnections operation:

n8n_update_partial_workflow({
  id: "workflow-id",
  operations: [{
    type: "cleanStaleConnections"
  }]
})
Example 3: Renamed Node Not Updated

Error:

{
  "type": "invalid_reference",
  "property": "expression",
  "message": "Node 'Get Weather' does not exist (did you mean 'Weather API'?)",
  "referenced_node": "Get Weather",
  "suggestions": ["Weather API"]
}

Broken Configuration:

{
  "value": "={{$node['Get Weather'].json.temperature}}"  // ❌ Old name
}

Fix:

{
  "value": "={{$node['Weather API'].json.temperature}}"  // ✅ Current name
}

Warnings (Should Fix)

6. best_practice

What it means: Configuration works but doesn't follow best practices

Severity: Warning (doesn't block execution) — surfaces under ai-friendly / strict only (n8n-mcp ≥ 2.63.0). Error-handling style is never a hard error.

When acceptable: Development, testing, simple workflows

When to fix: Production workflows, critical operations

Example 1: Missing Error Handling

Warning:

{
  "type": "best_practice",
  "property": "onError",
  "message": "Slack API can have rate limits and connection issues",
  "suggestion": "Add error handling: onError: 'continueRegularOutput'"
}

Current Configuration:

{
  "resource": "message",
  "operation": "post",
  "channel": "#alerts"
  // No error handling ⚠️
}

Recommended Fix (modern onError, set at node level):

{
  "onError": "continueRegularOutput",  // prefer this over deprecated continueOnFail
  "retryOnFail": true,                 // maxTries defaults to 3 — stating it isn't required
  "parameters": {
    "resource": "message",
    "operation": "post",
    "channel": "#alerts"
  }
}
Example 2: No Retry Logic

Warning:

{
  "type": "best_practice",
  "property": "retryOnFail",
  "message": "External API calls should retry on failure",
  "suggestion": "Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000"
}

When to ignore: Idempotent operations, APIs with their own retry logic

When to fix: Flaky external services, production automation


7. deprecated

What it means: Using a genuinely deprecated feature (e.g. continueOnFail: true — the validator suggests onError: 'continueRegularOutput' instead)

Severity: Warning (still works but may stop working in future) — surfaces under every profile

When to fix: Always (eventually)

Example 1: Outdated typeVersion (suggestion, not a deprecation)

Running an older-but-supported typeVersion is normal, supported n8n behavior — the vast majority of production workflows do. It is now a suggestion surfaced only under ai-friendly / strict (n8n-mcp ≥ 2.63.0), not a deprecation warning, and never an error. A typeVersion that never existed on a core node is still a hard error (it fails activation).

Suggestion (plain-string, ai-friendly / strict):

Outdated typeVersion for node "Slack": 1. Latest is 2.3.

Fix (optional — only if you want the newer node behavior):

{
  "type": "n8n-nodes-base.slack",
  "typeVersion": 2.3,  // ✅ Updated — may need config changes for the new version
}

n8n_autofix_workflow offers typeversion-upgrade (to latest, with migration) and typeversion-correction (downgrade an unsupported version to a real one).


8. performance

What it means: Configuration may cause performance issues

Severity: Warning

When to fix: High-volume workflows, large datasets

Example 1: Unbounded Query

Warning:

{
  "type": "performance",
  "property": "query",
  "message": "SELECT without LIMIT can return massive datasets",
  "suggestion": "Add LIMIT clause or use pagination"
}

Current:

SELECT * FROM users WHERE active = true

Fix:

SELECT * FROM users WHERE active = true LIMIT 1000

Auto-Sanitization Fixes

9. operator_structure

What it means: The exact shape of an IF/Switch/Filter condition (singleValue, conditions.options metadata)

Severity: Not a validation finding (n8n-mcp ≥ 2.63.0)

Auto-Fix: ✅ Normalized automatically on workflow save

Validation accepts a condition whether or not singleValue and the conditions.options sub-fields are present — n8n derives unary-ness from the operator name and defaults the metadata. The sanitizer still tidies these into the canonical form on save, so you never need to hand-write them either way. The before/after below is what the sanitizer produces, not something the validator errors on.

Still real errors (these are not auto-fixed — they change behavior): a legacy v1 operator name (e.g. smaller) inside a v2 structure ("Operation 'smaller' is not valid for type 'string'"), a v1-shaped conditions object on a v2 node (silently evaluates true), and a filter object with no conditions at all.

Normalized on Save: Binary Operators

Before (you create this):

{
  "type": "boolean",
  "operation": "equals",
  "singleValue": true  // ❌ Wrong for binary operator
}

After (auto-sanitization fixes it):

{
  "type": "boolean",
  "operation": "equals"
  // singleValue removed ✅
}

You don't need to do anything - this is fixed on save!

Normalized on Save: Unary Operators

Before:

{
  "type": "boolean",
  "operation": "isEmpty"
  // Missing singleValue ❌
}

After:

{
  "type": "boolean",
  "operation": "isEmpty",
  "singleValue": true  // ✅ Added automatically
}

What you should do: Trust auto-sanitization, don't manually fix these!


patchNodeField Errors

What it means: A patchNodeField operation failed during n8n_update_partial_workflow

The patchNodeField operation is strict by design — it errors instead of silently continuing when something is wrong. This catches mistakes early but means you need to handle these specific error cases.

Error: Find string not found

The patch's find value doesn't exist in the target field. This usually means the content was already changed, or the find string has a typo.

patchNodeField: find string not found in field "parameters.jsCode"

How to fix: Double-check the exact string. Use n8n_get_workflow to inspect the current field value. Whitespace and line endings matter — if unsure, use regex: true with \s+ for flexible whitespace matching.

Error: Ambiguous match (multiple occurrences)

The find string appears more than once in the field. Without replaceAll: true, this is treated as ambiguous and rejected.

patchNodeField: find string matches 3 times in field "parameters.jsCode" — set replaceAll: true to replace all, or use a more specific find string

How to fix: Either set replaceAll: true if you want to replace all occurrences, or make your find string more specific to match only the intended location.

Error: Invalid regex pattern

When regex: true, the pattern is validated for correctness and safety.

patchNodeField: invalid or unsafe regex pattern

How to fix: Check regex syntax. Nested quantifiers like (a+)+ and overlapping alternations like (\w|\d)+ are rejected as ReDoS risks. Simplify the pattern.


Auto-Sanitization: What It CANNOT Fix

Auto-sanitization handles operator structure (binary/unary singleValue, IF/Switch metadata) automatically on every save. It does not fix these — you must handle them manually:

Broken Connections

References to non-existent nodes.

Solution: Use the cleanStaleConnections operation in n8n_update_partial_workflow.

Branch Count Mismatches

3 Switch rules but only 2 output connections.

Solution: Add missing connections or remove extra rules.

Paradoxical Corrupt States

API returns corrupt data but rejects updates.

Solution: May require manual database intervention.


Recovery Patterns

Pattern 1: Progressive Validation

Problem: Too many errors at once

Solution:

// Step 1: Minimal valid config
let config = {
  resource: "message",
  operation: "post",
  channel: "#general",
  text: "Hello"
};

validate_node({nodeType: "nodes-base.slack", config, profile: "runtime"});
// ✅ Valid

// Step 2: Add features one by one
config.attachments = [...];
validate_node({nodeType: "nodes-base.slack", config, profile: "runtime"});

config.blocks = [...];
validate_node({nodeType: "nodes-base.slack", config, profile: "runtime"});
Pattern 2: Error Triage

Problem: Multiple errors

Solution:

const result = validate_node({...});

// 1. Fix errors (must fix)
result.errors.forEach(error => {
  console.log(`MUST FIX: ${error.property} - ${error.message}`);
});

// 2. Review warnings (should fix)
result.warnings.forEach(warning => {
  console.log(`SHOULD FIX: ${warning.property} - ${warning.message}`);
});

// 3. Consider suggestions (optional)
result.suggestions.forEach(sug => {
  console.log(`OPTIONAL: ${sug.message}`);
});
Pattern 3: Use get_node

Problem: Don't know what's required

Solution:

// Before configuring, check requirements
const info = get_node({
  nodeType: "nodes-base.slack"
});

// Look for required fields
info.properties.forEach(prop => {
  if (prop.required) {
    console.log(`Required: ${prop.name} (${prop.type})`);
  }
});

Summary

Most Common Errors:

  1. missing_required (45%) - Always check get_node

  2. invalid_value (28%) - Check allowed values

  3. type_mismatch (12%) - Use correct data types

  4. invalid_expression (8%) - Use Expression Syntax skill

  5. invalid_reference (5%) - Clean stale connections

Auto-Fixed:

  • operator_structure - Trust auto-sanitization!

Related Skills:

  • SKILL.md - Main validation guide

  • FALSE_POSITIVES.md - When to ignore warnings

  • n8n Expression Syntax - Fix expression errors

  • n8n MCP Tools Expert - Use validation tools correctly

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python, Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Romuald Czlonkowski
Publisher
Romuald Czlonkowski (Community)
Source
czlonkowski/n8n-skills · skills/n8n-validation-expert · commit e037563
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Romuald Czlonkowski or Romuald Czlonkowski.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-validation-expert.zip