# Error Catalog

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

---

## Error Types Overview

Common validation errors by priority:

| Error Type | Priority | Severity | Auto-Fix |
|---|---|---|---|
| missing_required | Highest | Error | ❌ |
| invalid_value | High | Error | ❌ |
| type_mismatch | Medium | Error | ❌ |
| invalid_expression | Medium | Error | ❌ |
| invalid_reference | Low | Error | ❌ |
| operator_structure | Lowest | Not 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**:
```json
{
  "type": "missing_required",
  "property": "channel",
  "message": "Channel name is required",
  "node": "Slack",
  "path": "parameters.channel"
}
```

**Broken Configuration**:
```javascript
{
  "resource": "message",
  "operation": "post"
  // Missing: channel
}
```

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

**How to identify required fields**:
```javascript
// 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**:
```json
{
  "type": "missing_required",
  "property": "url",
  "message": "URL is required for HTTP Request",
  "node": "HTTP Request",
  "path": "parameters.url"
}
```

**Broken Configuration**:
```javascript
{
  "method": "GET",
  "authentication": "none"
  // Missing: url
}
```

**Fix**:
```javascript
{
  "method": "GET",
  "authentication": "none",
  "url": "https://api.example.com/data"  // ✅ Added
}
```

#### Example 3: Database Query Missing Connection

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

**Broken Configuration**:
```javascript
{
  "operation": "executeQuery"
  // Missing: query
}
```

**Fix**:
```javascript
{
  "operation": "executeQuery",
  "query": "SELECT * FROM users WHERE active = true"  // ✅ Added
}
```

#### Example 4: Conditional Fields

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

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

**Fix**:
```javascript
{
  "method": "POST",
  "url": "https://api.example.com/create",
  "sendBody": true,
  "body": {
    "contentType": "json",
    "content": {
      "name": "John",
      "email": "john@example.com"
    }
  }  // ✅ 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**:
```json
{
  "type": "invalid_value",
  "property": "operation",
  "message": "Operation must be one of: post, update, delete, get",
  "current": "send",
  "allowed": ["post", "update", "delete", "get"]
}
```

**Broken Configuration**:
```javascript
{
  "resource": "message",
  "operation": "send"  // ❌ Invalid - should be "post"
}
```

**Fix**:
```javascript
{
  "resource": "message",
  "operation": "post"  // ✅ Use valid operation
}
```

#### Example 2: Invalid HTTP Method

**Error**:
```json
{
  "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**:
```javascript
{
  "method": "FETCH",  // ❌ Invalid
  "url": "https://api.example.com"
}
```

**Fix**:
```javascript
{
  "method": "GET",  // ✅ Use valid HTTP method
  "url": "https://api.example.com"
}
```

#### Example 3: Invalid Channel Format

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

**Broken Configuration**:
```javascript
{
  "resource": "message",
  "operation": "post",
  "channel": "General"  // ❌ Wrong format
}
```

**Fix**:
```javascript
{
  "resource": "message",
  "operation": "post",
  "channel": "#general"  // ✅ Correct format
}
```

#### Example 4: Invalid Enum with Case Sensitivity

**Error**:
```json
{
  "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**:
```javascript
{
  "resource": "Message",  // ❌ Capital M
  "operation": "post"
}
```

**Fix**:
```javascript
{
  "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**:
```json
{
  "type": "type_mismatch",
  "property": "limit",
  "message": "Expected number, got string",
  "expected": "number",
  "current": "100"
}
```

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

**Fix**:
```javascript
{
  "operation": "executeQuery",
  "query": "SELECT * FROM users",
  "limit": 100  // ✅ Number
}
```

#### Example 2: Number Instead of String

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

**Broken Configuration**:
```javascript
{
  "resource": "message",
  "operation": "post",
  "channel": 12345  // ❌ Number (even if channel ID)
}
```

**Fix**:
```javascript
{
  "resource": "message",
  "operation": "post",
  "channel": "#general"  // ✅ String (channel name, not ID)
}
```

#### Example 3: Boolean as String

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

**Broken Configuration**:
```javascript
{
  "method": "GET",
  "url": "https://api.example.com",
  "sendHeaders": "true"  // ❌ String "true"
}
```

**Fix**:
```javascript
{
  "method": "GET",
  "url": "https://api.example.com",
  "sendHeaders": true  // ✅ Boolean true
}
```

#### Example 4: Object Instead of Array

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

**Broken Configuration**:
```javascript
{
  "name": "New Channel",
  "tags": {"tag": "important"}  // ❌ Object
}
```

**Fix**:
```javascript
{
  "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**:
```json
{
  "type": "error",
  "property": "assignments.assignments[0].value",
  "message": "Expression requires = prefix to be evaluated",
  "current": "{{ $json.name }}"
}
```

**Broken Configuration**:
```javascript
{
  "text": "{{ $json.name }}"  // ❌ Has braces but no leading =
}
```

**Fix**:
```javascript
{
  "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**:
```json
{
  "type": "invalid_expression",
  "property": "value",
  "message": "Referenced node 'HTTP Requets' does not exist",
  "current": "={{$node['HTTP Requets'].json.data}}"
}
```

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

**Fix**:
```javascript
{
  "field": "data",
  "value": "={{$node['HTTP Request'].json.data}}"  // ✅ Correct node name
}
```

#### Example 3: Invalid Property Access

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

**Broken Configuration**:
```javascript
{
  "text": "={{$json.data.user.name}}"  // ❌ Structure doesn't exist
}
```

**Fix** (with safe navigation):
```javascript
{
  "text": "={{$json.data?.user?.name || 'Unknown'}}"  // ✅ Safe navigation + fallback
}
```

#### Example 4: Webhook Data Access Error

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

**Common Gotcha**: Webhook data is under `.body`!

**Broken Configuration**:
```javascript
{
  "field": "email",
  "value": "={{$json.email}}"  // ❌ Missing .body
}
```

**Fix**:
```javascript
{
  "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**:
```json
{
  "type": "invalid_reference",
  "property": "expression",
  "message": "Node 'Transform Data' does not exist in workflow",
  "referenced_node": "Transform Data"
}
```

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

**Fix**:
```javascript
// 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**:
```json
{
  "type": "invalid_reference",
  "message": "Connection references node 'Slack1' which does not exist",
  "source": "HTTP Request",
  "target": "Slack1"
}
```

**Fix**: Use `cleanStaleConnections` operation:
```javascript
n8n_update_partial_workflow({
  id: "workflow-id",
  operations: [{
    type: "cleanStaleConnections"
  }]
})
```

#### Example 3: Renamed Node Not Updated

**Error**:
```json
{
  "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**:
```javascript
{
  "value": "={{$node['Get Weather'].json.temperature}}"  // ❌ Old name
}
```

**Fix**:
```javascript
{
  "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**:
```json
{
  "type": "best_practice",
  "property": "onError",
  "message": "Slack API can have rate limits and connection issues",
  "suggestion": "Add error handling: onError: 'continueRegularOutput'"
}
```

**Current Configuration**:
```javascript
{
  "resource": "message",
  "operation": "post",
  "channel": "#alerts"
  // No error handling ⚠️
}
```

**Recommended Fix** (modern `onError`, set at node level):
```javascript
{
  "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**:
```json
{
  "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):
```javascript
{
  "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**:
```json
{
  "type": "performance",
  "property": "query",
  "message": "SELECT without LIMIT can return massive datasets",
  "suggestion": "Add LIMIT clause or use pagination"
}
```

**Current**:
```sql
SELECT * FROM users WHERE active = true
```

**Fix**:
```sql
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):
```javascript
{
  "type": "boolean",
  "operation": "equals",
  "singleValue": true  // ❌ Wrong for binary operator
}
```

**After** (auto-sanitization fixes it):
```javascript
{
  "type": "boolean",
  "operation": "equals"
  // singleValue removed ✅
}
```

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

#### Normalized on Save: Unary Operators

**Before**:
```javascript
{
  "type": "boolean",
  "operation": "isEmpty"
  // Missing singleValue ❌
}
```

**After**:
```javascript
{
  "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**:
```javascript
// 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**:
```javascript
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**:
```javascript
// 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](SKILL.md)** - Main validation guide
- **[FALSE_POSITIVES.md](FALSE_POSITIVES.md)** - When to ignore warnings
- **n8n Expression Syntax** - Fix expression errors
- **n8n MCP Tools Expert** - Use validation tools correctly
