
N8n Node Configuration
- 5.8k installs
- 6k repo stars
- Updated August 4, 2026
- czlonkowski/n8n-skills
Expert guidance for configuring n8n nodes with operation-aware field requirements, property dependencies, and progressive discovery patterns.
About
n8n Node Configuration provides expert guidance for setting up nodes with correct required fields based on operation context and property dependencies. Developers use this when configuring Slack, HTTP Request, database, and conditional logic nodes where required fields vary by operation and visibility is controlled by displayOptions rules. Key workflows include progressive discovery using get_node detail levels (standard covers 95% of cases), iterative validation to surface conditional requirements, and understanding how resource+operation determine field visibility and constraints. The skill covers operation-specific patterns (Slack post vs update), property dependencies (HTTP POST sendBody controls body visibility), and anti-patterns like over-configuration upfront or skipping validation cycles. --- name: n8n-node-configuration description: Operation-aware node configuration guidance. Use when configuring nodes, understanding property dependencies, determining required fields, choosing between get_node detail levels, or learning common configuration patterns by node type. Always use this skill when setting up node parameters - it explains which fields are required for each ope.
- Operation-aware configuration: resource+operation determine required fields, not universal schemas
- Progressive discovery with get_node detail levels - start with standard (~1-2K tokens), escalate to full or search_prope
- Property dependencies via displayOptions: fields show/hide based on other field values (e.g. sendBody=true unlocks body)
- Validate iteratively: configure minimal fields, validate, fix errors, repeat (avg 2-3 cycles) rather than over-configuri
- Silent-failure gotchas by node family: Switch fallbackOutput, Merge numberOfInputs, Database SQL injection, Slack Block
N8n Node Configuration by the numbers
- 5,773 all-time installs (skills.sh)
- +170 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #62 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
n8n-node-configuration capabilities & compatibility
- Capabilities
- operation aware field requirement resolution · property dependency discovery and explanation · silent failure pattern identification by node fa · progressive detail level selection for get_node · validation driven configuration iteration guidan
- Use cases
- api development
- Platforms
- macOS · Windows · Linux
- Runs
- Remote server
- Pricing
- Free
npx skills add https://github.com/czlonkowski/n8n-skills --skill n8n-node-configurationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5.8k |
|---|---|
| repo stars | ★ 6k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | czlonkowski/n8n-skills ↗ |
What it does
Configure n8n nodes with operation-aware field requirements and property dependencies for workflow automation.
Who is it for?
Configuring n8n nodes with complex property dependencies, understanding operation-specific requirements, and troubleshooting silent failures during workflow setup.
Skip if: Beginners new to n8n (start with basic node setup first), debugging expression syntax (use n8n Expression Syntax skill), or interpreting validation error messages (use n8n Validation Expert).
When should I use this skill?
Setting up node parameters, determining which fields are required for an operation, understanding why a field is not visible, learning common patterns for resource/operation nodes, or configuring conditional logic.
What you get
Developers configure nodes correctly on first pass by understanding operation context, using appropriate detail levels, validating iteratively, and avoiding silent-failure gotchas.
- Correctly configured node with all required fields for target operation
- Node passes validate_node without errors
- Understanding of why each field is required and when it applies
By the numbers
- Standard detail level covers 95% of configuration use cases
- Average 56 seconds between configuration edits
- Typical configuration iteration cycle takes 2-3 validation loops
Files
n8n Node Configuration
Expert guidance for operation-aware node configuration with property dependencies.
---
Configuration Philosophy
Progressive disclosure: Start minimal, add complexity as needed
Configuration best practices:
get_nodewithdetail: "standard"is the most used discovery pattern- 56 seconds average between configuration edits
- Covers 95% of use cases with 1-2K tokens response
Key insight: Most configurations need only standard detail, not full schema!
---
Core Concepts
1. Operation-Aware Configuration
Not all fields are always required - it depends on operation!
Example: Slack node
// For operation='post'
{
"resource": "message",
"operation": "post",
"channel": "#general", // Required for post
"text": "Hello!" // Required for post
}
// For operation='update'
{
"resource": "message",
"operation": "update",
"messageId": "123", // Required for update (different!)
"text": "Updated!" // Required for update
// channel NOT required for update
}Key: Resource + operation determine which fields are required!
2. Property Dependencies
Fields appear/disappear based on other field values
Example: HTTP Request node
// When method='GET'
{
"method": "GET",
"url": "https://api.example.com"
// sendBody not shown (GET doesn't have body)
}
// When method='POST'
{
"method": "POST",
"url": "https://api.example.com",
"sendBody": true, // Now visible!
"body": { // Required when sendBody=true
"contentType": "json",
"content": {...}
}
}Mechanism: displayOptions control field visibility
3. Progressive Discovery
Use the right detail level:
1. get_node({detail: "standard"}) - DEFAULT
- Quick overview (~1-2K tokens)
- Required fields + common options
- Use first - covers 95% of needs
2. get_node({mode: "search_properties", propertyQuery: "..."}) (for finding specific fields)
- Find properties by name
- Use when looking for auth, body, headers, etc.
3. get_node({detail: "full"}) (complete schema)
- All properties (~3-8K tokens)
- Use only when standard detail is insufficient
---
Configuration Workflow
Standard Process
1. Identify node type and operation. 2. Use get_node (standard detail is default). 3. Configure required fields. 4. Validate configuration. 5. If a field is unclear → get_node({mode: "search_properties"}). 6. Add optional fields as needed. 7. Validate again. 8. Deploy.
Example: Configuring HTTP Request
The validate-driven loop in practice: start minimal (method, url, authentication), then let each validate_node error surface the next required field (sendBody for POST → body when sendBody=true) until valid. Full step-by-step walkthrough in [OPERATION_PATTERNS.md](OPERATION_PATTERNS.md#worked-example-configuring-http-request-step-by-step).
---
get_node Detail Levels
Standard Detail (DEFAULT - Use This!)
✅ Starting configuration
get_node({
nodeType: "nodes-base.slack"
});
// detail="standard" is the defaultReturns (~1-2K tokens):
- Required fields
- Common options
- Operation list
- Metadata
Use: 95% of configuration needs
Full Detail (Use Sparingly)
✅ When standard isn't enough
get_node({
nodeType: "nodes-base.slack",
detail: "full"
});Returns (~3-8K tokens):
- Complete schema
- All properties
- All nested options
Warning: Large response, use only when standard insufficient
Search Properties Mode
✅ Looking for specific field
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "auth"
});Use: Find authentication, headers, body fields, etc.
Decision Tree
1. Starting a new node config → get_node (standard). 2. Standard has what you need → configure with it. Otherwise continue. 3. Looking for a specific field → search_properties mode. Otherwise continue. 4. Still need more → get_node({detail: "full"}).
---
Property Dependencies Deep Dive
Fields have displayOptions visibility rules: show/hide blocks where multiple conditions are AND'd and multiple values are OR'd (e.g. body shows when sendBody=true AND method IN (POST, PUT, PATCH)). The three recurring patterns are the boolean toggle (sendBody → body), the operation switch (post vs update show different fields), and type selection (string vs boolean conditions). To find what controls a field, use get_node({mode: "search_properties", propertyQuery: "..."}) or get_node({detail: "full"}) — especially when validation flags a field you don't see.
Mechanism details, all four dependency patterns, complex flows, nested dependencies, and troubleshooting are in [DEPENDENCIES.md](DEPENDENCIES.md) (quick-reference recap under Quick Reference: displayOptions and Common Dependency Patterns).
---
Common Node Patterns
Pattern 1: Resource/Operation Nodes
Examples: Slack, Google Sheets, Airtable
Structure:
{
"resource": "<entity>", // What type of thing
"operation": "<action>", // What to do with it
// ... operation-specific fields
}How to configure: 1. Choose resource 2. Choose operation 3. Use get_node to see operation-specific requirements 4. Configure required fields
Pattern 2: HTTP-Based Nodes
Examples: HTTP Request, Webhook
Structure:
{
"method": "<HTTP_METHOD>",
"url": "<endpoint>",
"authentication": "<type>",
// ... method-specific fields
}Dependencies:
- POST/PUT/PATCH → sendBody available
- sendBody=true → body required
- authentication != "none" → credentials required
Critical: credentials block, node id, typeVersion
- Never set a placeholder credential ID (e.g.
"id": "REPLACE_ME") — n8n's UI renders a permanently disabled credential selector for unknown IDs. Omit thecredentialsblock when the real ID is unknown; the user then gets a normal clickable dropdown. - Node `id` must be a UUID v4, not a readable slug — the frontend binds forms and the credential component to it.
- Don't hardcode old `typeVersion` values — verify the current version with
get_node(httpRequest is 4.4+).
Pattern 3: Database Nodes
Examples: Postgres, MySQL, MongoDB
Structure:
{
"operation": "<query|insert|update|delete>",
// ... operation-specific fields
}Dependencies:
- operation="executeQuery" → query required
- operation="insert" → table + values required
- operation="update" → table + values + where required
Critical: Write operations may return 0 items
- INSERT, UPDATE, DELETE can produce 0 n8n output items, depending on the node and operation (raw query execution reliably returns 0 result rows; some database nodes return the affected rows)
- Set
alwaysOutputData: trueon write-operation nodes to keep downstream chains alive - Downstream nodes should use
$('UpstreamNode').all()instead of$inputif they need data
Pattern 4: Conditional Logic Nodes
Examples: IF, Switch, Merge
Structure:
{
"conditions": {
"<type>": [
{
"operation": "<operator>",
"value1": "...",
"value2": "..." // Only for binary operators
}
]
}
}Dependencies:
- Binary operators (equals, contains, etc.) → value1 + value2
- Unary operators (isEmpty, isNotEmpty) → value1 only + singleValue: true
---
Operation-Specific Configuration
Required fields shift with resource + operation: Slack post needs channel+text, but update needs messageId+text (channel optional) and channel/create needs name. HTTP GET uses sendQuery+queryParameters; POST needs sendBody+body. IF binary operators (equals) need value1+value2; unary (isEmpty) need only value1 plus auto-added singleValue: true. Concrete minimal configs for each in [OPERATION_PATTERNS.md](OPERATION_PATTERNS.md#operation-specific-configuration-examples).
---
Handling Conditional Requirements
Some fields are required only under certain conditions: HTTP body is required when sendBody=true AND method IN (POST, PUT, PATCH, DELETE); IF singleValue should be true when the operator is unary (isEmpty, isNotEmpty, true, false) — and auto-sanitization sets it for you. Discover conditional requirements by reading the validation error, searching the property (get_node({mode: "search_properties"})), or iterating from a minimal config. Worked discovery examples in [DEPENDENCIES.md](DEPENDENCIES.md#handling-conditional-requirements).
---
Node-Specific Configuration Notes
SplitInBatches v3
{
"batchSize": 100, // Number of items per batch
"options": {}
}Output wiring:
main[0](done) → Connect to downstream processing (add Limit 1 first)main[1](each batch) → Connect to loop body, then loop back to SplitInBatches input
See the n8n Workflow Patterns skill for detailed loop and nested loop patterns.
Google Sheets Node
Per-item execution: Each input item triggers a separate API call. If you have 100 items and use a Google Sheets "Append Row" node, it makes 100 API calls. To write in bulk, aggregate items in a Code node first, then use a single HTTP Request with the Sheets API.
Formula columns: Never use append on sheets with formula columns — it overwrites formulas. Instead, use HTTP Request with Google Sheets API values.update (PUT) method and a googleApi credential.
---
Configuration Anti-Patterns
❌ Don't: Over-configure Upfront
Bad:
// Adding every possible field
{
"method": "GET",
"url": "...",
"sendQuery": false,
"sendHeaders": false,
"sendBody": false,
"timeout": 10000,
"ignoreResponseCode": false,
// ... 20 more optional fields
}Good:
// Start minimal
{
"method": "GET",
"url": "...",
"authentication": "none"
}
// Add fields only when needed❌ Don't: Skip Validation
Bad:
// Configure and deploy without validating
const config = {...};
n8n_update_partial_workflow({...}); // YOLOGood:
// Validate before deploying
const config = {...};
const result = validate_node({...});
if (result.valid) {
n8n_update_partial_workflow({...});
}❌ Don't: Ignore Operation Context
Bad:
// Same config for all Slack operations
{
"resource": "message",
"operation": "post",
"channel": "#general",
"text": "..."
}
// Then switching operation without updating config
{
"resource": "message",
"operation": "update", // Changed
"channel": "#general", // Wrong field for update!
"text": "..."
}Good:
// Check requirements when changing operation
get_node({
nodeType: "nodes-base.slack"
});
// See what update operation needs (messageId, not channel)---
Surgical Field Edits with patchNodeField
When you need to edit a specific string inside a node field — rather than replacing the whole field — use patchNodeField in n8n_update_partial_workflow. This is especially useful for:
- Editing code inside Code nodes without re-transmitting the full code block
- Updating URLs or text in large HTML email templates
- Fixing typos in JSON bodies or long text fields
// Instead of replacing the entire jsCode field:
n8n_update_partial_workflow({
id: "wf-123",
operations: [{
type: "patchNodeField",
nodeName: "Code",
fieldPath: "parameters.jsCode",
patches: [{find: "const limit = 10;", replace: "const limit = 50;"}]
}]
})patchNodeField is strict — it errors if the find string isn't found or matches multiple times (unless replaceAll: true). This prevents accidental silent failures during configuration updates. See the n8n MCP Tools Expert skill for full syntax and examples.
---
Best Practices
Do
1. Start with get_node (standard detail)
- ~1-2K tokens response
- Covers 95% of configuration needs
- Default detail level
2. Validate iteratively
- Configure → Validate → Fix → Repeat
- Average 2-3 iterations is normal
- Read validation errors carefully
3. Use search_properties mode when stuck
- If field seems missing, search for it
- Understand what controls field visibility
get_node({mode: "search_properties", propertyQuery: "..."})
4. Respect operation context
- Different operations = different requirements
- Always check get_node when changing operation
- Don't assume configs are transferable
5. Trust auto-sanitization
- Operator structure fixed automatically
- Don't manually add/remove singleValue
- IF/Switch metadata added on save
❌ Don't
1. Jump to detail="full" immediately
- Try standard detail first
- Only escalate if needed
- Full schema is 3-8K tokens
2. Configure blindly
- Always validate before deploying
- Understand why fields are required
- Use search_properties for conditional fields
3. Copy configs without understanding
- Different operations need different fields
- Validate after copying
- Adjust for new context
4. Manually fix auto-sanitization issues
- Let auto-sanitization handle operator structure
- Focus on business logic
- Save and let system fix structure
---
Silent-Failure Gotchas by Node Family
Some misconfigurations pass validate_node and validate_workflow clean, run without error, and quietly do the wrong thing — get_node shows the fields exist but not what happens when you omit them. The high-frequency ones:
- Switch — no
options.fallbackOutput⇒ unmatched items silently dropped. - Merge —
numberOfInputsdefaults to 2 (extra sources drop);useDataOfInputis 1-indexed vs the 0-indexedconnections.<src>.main[idx]slot (useDataOfInput: "N"→main[N-1]). - Database —
{{ }}interpolation intoparameters.queryis SQL injection; use$1/$2placeholders +options.queryReplacement. - Slack — Block Kit must be wrapped
={{ { "blocks": ... } }}or it posts as plain text. - Webhook / Respond —
responseCodedefaults to 200 even on error branches. - Schedule Trigger — timezone is workflow-level (Workflow Settings), not per-rule.
Full symptom/cause/fix detail (in JSON + n8n_update_partial_workflow terms) in [NODE_FAMILY_GOTCHAS.md](NODE_FAMILY_GOTCHAS.md).
---
Detailed References
For comprehensive guides on specific topics:
- [DEPENDENCIES.md](DEPENDENCIES.md) - Deep dive into property dependencies and displayOptions
- [OPERATION_PATTERNS.md](OPERATION_PATTERNS.md) - Common configuration patterns by node type
- [NODE_FAMILY_GOTCHAS.md](NODE_FAMILY_GOTCHAS.md) - Silent runtime traps by family (Switch, Merge, Database, Slack, Webhook, Schedule)
---
Summary
Configuration Strategy: 1. Start with get_node (standard detail is default) 2. Configure required fields for operation 3. Validate configuration 4. Search properties if stuck 5. Iterate until valid (avg 2-3 cycles) 6. Deploy with confidence
Key Principles:
- Operation-aware: Different operations = different requirements
- Progressive disclosure: Start minimal, add as needed
- Dependency-aware: Understand field visibility rules
- Validation-driven: Let validation guide configuration
Related Skills:
- n8n MCP Tools Expert - How to use discovery tools correctly
- n8n Validation Expert - Interpret validation errors
- n8n Expression Syntax - Configure expression fields
- n8n Workflow Patterns - Apply patterns with proper configuration
Property Dependencies Guide
Deep dive into n8n property dependencies and displayOptions mechanism.
---
What Are Property Dependencies?
Definition: Rules that control when fields are visible or required based on other field values.
Mechanism: displayOptions in node schema
Purpose:
- Show relevant fields only
- Hide irrelevant fields
- Simplify configuration UX
- Prevent invalid configurations
---
displayOptions Structure
Basic Format
{
"name": "fieldName",
"type": "string",
"displayOptions": {
"show": {
"otherField": ["value1", "value2"]
}
}
}Translation: Show fieldName when otherField equals "value1" OR "value2"
Show vs Hide
show (Most Common)
Show field when condition matches:
{
"name": "body",
"displayOptions": {
"show": {
"sendBody": [true]
}
}
}Meaning: Show body when sendBody = true
hide (Less Common)
Hide field when condition matches:
{
"name": "advanced",
"displayOptions": {
"hide": {
"simpleMode": [true]
}
}
}Meaning: Hide advanced when simpleMode = true
Multiple Conditions (AND Logic)
{
"name": "body",
"displayOptions": {
"show": {
"sendBody": [true],
"method": ["POST", "PUT", "PATCH"]
}
}
}Meaning: Show body when:
sendBody = trueANDmethod IN (POST, PUT, PATCH)
All conditions must match (AND logic)
Multiple Values (OR Logic)
{
"name": "someField",
"displayOptions": {
"show": {
"method": ["POST", "PUT", "PATCH"]
}
}
}Meaning: Show someField when:
method = POSTORmethod = PUTORmethod = PATCH
Any value matches (OR logic)
---
Common Dependency Patterns
Pattern 1: Boolean Toggle
Use case: Optional feature flag
Example: HTTP Request sendBody
// Field: sendBody (boolean)
{
"name": "sendBody",
"type": "boolean",
"default": false
}
// Field: body (depends on sendBody)
{
"name": "body",
"displayOptions": {
"show": {
"sendBody": [true]
}
}
}Flow: 1. User sees sendBody checkbox 2. When checked → body field appears 3. When unchecked → body field hides
Pattern 2: Resource/Operation Cascade
Use case: Different operations show different fields
Example: Slack message operations
// Operation: post
{
"name": "channel",
"displayOptions": {
"show": {
"resource": ["message"],
"operation": ["post"]
}
}
}
// Operation: update
{
"name": "messageId",
"displayOptions": {
"show": {
"resource": ["message"],
"operation": ["update"]
}
}
}Flow: 1. User selects resource="message" 2. User selects operation="post" → sees channel 3. User changes to operation="update" → channel hides, messageId shows
Pattern 3: Type-Specific Configuration
Use case: Different types need different fields
Example: IF node conditions
// String operations
{
"name": "value2",
"displayOptions": {
"show": {
"conditions.string.0.operation": ["equals", "notEquals", "contains"]
}
}
}
// Unary operations (isEmpty) don't show value2
{
"displayOptions": {
"hide": {
"conditions.string.0.operation": ["isEmpty", "isNotEmpty"]
}
}
}Pattern 4: Method-Specific Fields
Use case: HTTP methods have different options
Example: HTTP Request
// Query parameters (all methods can have)
{
"name": "queryParameters",
"displayOptions": {
"show": {
"sendQuery": [true]
}
}
}
// Body (only certain methods)
{
"name": "body",
"displayOptions": {
"show": {
"sendBody": [true],
"method": ["POST", "PUT", "PATCH", "DELETE"]
}
}
}---
Finding Property Dependencies
Using get_node with search_properties Mode
// Find properties related to "body"
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "body"
});Using get_node with Full Detail
// Get complete schema with displayOptions
get_node({
nodeType: "nodes-base.httpRequest",
detail: "full"
});When to Use
✅ Use when:
- Validation fails with "missing field" but you don't see that field
- A field appears/disappears unexpectedly
- You need to understand what controls field visibility
- Building dynamic configuration tools
❌ Don't use when:
- Simple configuration (use
get_nodewith standard detail) - Just starting configuration
- Field requirements are obvious
---
Complex Dependency Examples
Example 1: HTTP Request Complete Flow
Scenario: Configuring POST with JSON body
Step 1: Set method
{
"method": "POST"
// → sendBody becomes visible
}Step 2: Enable body
{
"method": "POST",
"sendBody": true
// → body field becomes visible AND required
}Step 3: Configure body
{
"method": "POST",
"sendBody": true,
"body": {
"contentType": "json"
// → content field becomes visible AND required
}
}Step 4: Add content
{
"method": "POST",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"name": "John",
"email": "john@example.com"
}
}
}
// ✅ Valid!Dependency chain:
method=POST
→ sendBody visible
→ sendBody=true
→ body visible + required
→ body.contentType=json
→ body.content visible + requiredExample 2: IF Node Operator Dependencies
Scenario: String comparison with different operators
Binary operator (equals):
{
"conditions": {
"string": [
{
"operation": "equals"
// → value1 required
// → value2 required
// → singleValue should NOT be set
}
]
}
}Unary operator (isEmpty):
{
"conditions": {
"string": [
{
"operation": "isEmpty"
// → value1 required
// → value2 should NOT be set
// → singleValue should be true (auto-added)
}
]
}
}Dependency table:
| Operator | value1 | value2 | singleValue |
|---|---|---|---|
| equals | Required | Required | false |
| notEquals | Required | Required | false |
| contains | Required | Required | false |
| isEmpty | Required | Hidden | true |
| isNotEmpty | Required | Hidden | true |
Example 3: Slack Operation Matrix
Scenario: Different Slack operations show different fields
// post message
{
"resource": "message",
"operation": "post"
// Shows: channel (required), text (required), attachments, blocks
}
// update message
{
"resource": "message",
"operation": "update"
// Shows: messageId (required), text (required), channel (optional)
}
// delete message
{
"resource": "message",
"operation": "delete"
// Shows: messageId (required), channel (required)
// Hides: text, attachments, blocks
}
// get message
{
"resource": "message",
"operation": "get"
// Shows: messageId (required), channel (required)
// Hides: text, attachments, blocks
}Field visibility matrix:
| Field | post | update | delete | get |
|---|---|---|---|---|
| channel | Required | Optional | Required | Required |
| text | Required | Required | Hidden | Hidden |
| messageId | Hidden | Required | Required | Required |
| attachments | Optional | Optional | Hidden | Hidden |
| blocks | Optional | Optional | Hidden | Hidden |
---
Nested Dependencies
What Are They?
Definition: Dependencies within object properties
Example: HTTP Request body.contentType controls body.content structure
{
"body": {
"contentType": "json",
// → content expects JSON object
"content": {
"key": "value"
}
}
}
{
"body": {
"contentType": "form-data",
// → content expects form fields array
"content": [
{
"name": "field1",
"value": "value1"
}
]
}
}How to Handle
Strategy: Configure parent first, then children
// Step 1: Parent
{
"body": {
"contentType": "json" // Set parent first
}
}
// Step 2: Children (structure determined by parent)
{
"body": {
"contentType": "json",
"content": { // JSON object format
"key": "value"
}
}
}---
Auto-Sanitization and Dependencies
What Auto-Sanitization Fixes
Operator structure issues (IF/Switch nodes):
Example: singleValue property
// You configure (missing singleValue)
{
"type": "boolean",
"operation": "isEmpty"
// Missing singleValue
}
// Auto-sanitization adds it
{
"type": "boolean",
"operation": "isEmpty",
"singleValue": true // ✅ Added automatically
}What It Doesn't Fix
Missing required fields:
// You configure (missing channel)
{
"resource": "message",
"operation": "post",
"text": "Hello"
// Missing required field: channel
}
// Auto-sanitization does NOT add
// You must add it yourself
{
"resource": "message",
"operation": "post",
"channel": "#general", // ← You must add
"text": "Hello"
}---
Troubleshooting Dependencies
Problem 1: "Field X is required but not visible"
Error:
{
"type": "missing_required",
"property": "body",
"message": "body is required"
}But you don't see body field in configuration!
Solution:
// Check field dependencies using search_properties
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "body"
});
// Find that body shows when sendBody=true
// Add sendBody
{
"method": "POST",
"sendBody": true, // ← Now body appears!
"body": {...}
}Problem 2: "Field disappears when I change operation"
Scenario:
// Working configuration
{
"resource": "message",
"operation": "post",
"channel": "#general",
"text": "Hello"
}
// Change operation
{
"resource": "message",
"operation": "update", // Changed
"channel": "#general", // Still here
"text": "Updated" // Still here
// Missing: messageId (required for update!)
}Validation error: "messageId is required"
Why: Different operation = different required fields
Solution:
// Check requirements for new operation
get_node({
nodeType: "nodes-base.slack"
});
// Configure for update operation
{
"resource": "message",
"operation": "update",
"messageId": "1234567890", // Required for update
"text": "Updated",
"channel": "#general" // Optional for update
}Problem 3: "Validation passes but field doesn't save"
Scenario: Field hidden by dependencies after validation
Example:
// Configure
{
"method": "GET",
"sendBody": true, // ❌ GET doesn't support body
"body": {...} // This will be stripped
}
// After save
{
"method": "GET"
// body removed because method=GET hides it
}Solution: Respect dependencies from the start
// Correct approach - check property dependencies
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "body"
});
// See that body only shows for POST/PUT/PATCH/DELETE
// Use correct method
{
"method": "POST",
"sendBody": true,
"body": {...}
}---
Advanced Patterns
Pattern 1: Conditional Required with Fallback
Example: Channel can be string OR expression
// Option 1: String
{
"channel": "#general"
}
// Option 2: Expression
{
"channel": "={{$json.channelName}}"
}
// Validation accepts bothPattern 2: Mutually Exclusive Fields
Example: Use either ID or name, not both
// Use messageId
{
"messageId": "1234567890"
// name not needed
}
// OR use messageName
{
"messageName": "thread-name"
// messageId not needed
}
// Dependencies ensure only one is requiredPattern 3: Progressive Complexity
Example: Simple mode vs advanced mode
// Simple mode
{
"mode": "simple",
"text": "{{$json.message}}"
// Advanced fields hidden
}
// Advanced mode
{
"mode": "advanced",
"attachments": [...],
"blocks": [...],
"metadata": {...}
// Simple field hidden, advanced fields shown
}---
Best Practices
✅ Do
1. Check dependencies when stuck
get_node({nodeType: "...", mode: "search_properties", propertyQuery: "..."});2. Configure parent properties first
// First: method, resource, operation
// Then: dependent fields3. Validate after changing operation
// Operation changed → requirements changed
validate_node({nodeType: "...", config: {...}, profile: "runtime"});4. Read validation errors for dependency hints
Error: "body required when sendBody=true"
→ Hint: Set sendBody=true to enable body❌ Don't
1. Don't ignore dependency errors
// Error: "body not visible" → Check displayOptions2. Don't hardcode all possible fields
// Bad: Adding fields that will be hidden3. Don't assume operations are identical
// Each operation has unique requirements---
Summary
Key Concepts:
displayOptionscontrol field visibilityshow= field appears when conditions matchhide= field disappears when conditions match- Multiple conditions = AND logic
- Multiple values = OR logic
Common Patterns: 1. Boolean toggle (sendBody → body) 2. Resource/operation cascade (different operations → different fields) 3. Type-specific config (string vs boolean conditions) 4. Method-specific fields (GET vs POST)
Troubleshooting:
- Field required but not visible → Check dependencies
- Field disappears after change → Operation changed requirements
- Field doesn't save → Hidden by dependencies
Tools:
get_node({mode: "search_properties"})- Find property dependenciesget_node({detail: "full"})- See complete schema with displayOptionsget_node- See operation requirements (standard detail)- Validation errors - Hints about dependencies
Related Files:
- [SKILL.md](SKILL.md) - Main configuration guide
- [OPERATION_PATTERNS.md](OPERATION_PATTERNS.md) - Common patterns by node type
---
Quick Reference: displayOptions and Common Dependency Patterns
A condensed introduction to the displayOptions mechanism and the three most common dependency patterns, plus how to find them.
displayOptions Mechanism
Fields have visibility rules:
{
"name": "body",
"displayOptions": {
"show": {
"sendBody": [true],
"method": ["POST", "PUT", "PATCH"]
}
}
}Translation: "body" field shows when:
- sendBody = true AND
- method = POST, PUT, or PATCH
Common Dependency Patterns
Pattern 1: Boolean Toggle
Example: HTTP Request sendBody
// sendBody controls body visibility
{
"sendBody": true // → body field appears
}Pattern 2: Operation Switch
Example: Slack resource/operation
// Different operations → different fields
{
"resource": "message",
"operation": "post"
// → Shows: channel, text, attachments, etc.
}
{
"resource": "message",
"operation": "update"
// → Shows: messageId, text (different fields!)
}Pattern 3: Type Selection
Example: IF node conditions
{
"type": "string",
"operation": "contains"
// → Shows: value1, value2
}
{
"type": "boolean",
"operation": "equals"
// → Shows: value1, value2, different operators
}Finding Property Dependencies
Use get_node with search_properties mode:
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "body"
});
// Returns property paths matching "body" with descriptionsOr use full detail for complete schema:
get_node({
nodeType: "nodes-base.httpRequest",
detail: "full"
});
// Returns complete schema with displayOptions rulesUse this when: Validation fails and you don't understand why field is missing/required
---
Handling Conditional Requirements
How to discover and satisfy fields that are required only under certain conditions.
Example: HTTP Request Body
Scenario: body field required, but only sometimes
Rule:
body is required when:
- sendBody = true AND
- method IN (POST, PUT, PATCH, DELETE)How to discover:
// Option 1: Read validation error
validate_node({...});
// Error: "body required when sendBody=true"
// Option 2: Search for the property
get_node({
nodeType: "nodes-base.httpRequest",
mode: "search_properties",
propertyQuery: "body"
});
// Shows: body property with displayOptions rules
// Option 3: Try minimal config and iterate
// Start without body, validation will tell you if neededExample: IF Node singleValue
Scenario: singleValue property appears for unary operators
Rule:
singleValue should be true when:
- operation IN (isEmpty, isNotEmpty, true, false)Good news: Auto-sanitization fixes this!
Manual check:
get_node({
nodeType: "nodes-base.if",
detail: "full"
});
// Shows complete schema with operator-specific rulesNode Family Gotchas
Silent-failure traps grouped by node family. These don't show up in validate_node or validate_workflow — the workflow validates clean, runs without error, and quietly does the wrong thing. get_node shows you the fields exist; it doesn't tell you what happens when you leave them off. This file covers the consequence.
Each entry: symptom (what you see at runtime), cause (why), fix (in n8n-mcp / JSON terms).
Contents
- Switch — dropped items on the unmatched path
- Merge — wrong input count and the 1-vs-0 index trap
- Database (Postgres / MySQL / Supabase) — SQL injection, transactions, no-rows
- Slack — Block Kit, threads, operation values
- Webhook / Respond to Webhook — response codes and modes
- Schedule Trigger — timezone, cron fields, missed runs
---
Switch
Symptom: items that match none of the rules vanish. No error, no warning — the workflow just loses data on the unmatched path.
Cause: without a fallback output, the Switch has nowhere to send unmatched items, so it discards them.
Fix: set options.fallbackOutput: "extra" and give it a name with options.renameFallbackOutput. While you're there, name every rule output too — unnamed 0 / 1 / 2 outputs are unreadable a month later, and a failure on "output 2" tells the operator nothing.
{
"parameters": {
"mode": "rules",
"rules": {
"values": [
{ "outputKey": "Paid", "renameOutput": true, "conditions": { "...": "..." } },
{ "outputKey": "Refunded", "renameOutput": true, "conditions": { "...": "..." } }
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "Unexpected"
}
}
}Apply surgically with patchNodeField on parameters.options.fallbackOutput, or with updateNode for the full options object. After wiring, confirm the fallback branch goes somewhere real (a log, an alert, a NoOp) — an enabled fallback that connects to nothing drops items just the same.
---
Merge
Two traps, both silent. They live on different Merge modes — numberOfInputs on Append/Combine, useDataOfInput on Choose Branch — so in practice you hit one or the other, not both.
Trap 1: input count defaults to 2
Symptom: you wire 3+ sources into a Merge, the canvas shows three wires going in, the workflow validates and runs — but only the first two sources' items appear downstream. The third silently drops.
Cause: numberOfInputs defaults to 2. The third wire connects to an input slot that doesn't exist on the node.
Fix: set numberOfInputs to match your wire count.
{ "parameters": { "mode": "append", "numberOfInputs": 3 } }Verify with get_node for the merge node on the user's n8n version — the field name has shifted across versions. After building, pull the workflow with n8n_get_workflow and confirm parameters.numberOfInputs matches the number of source entries in the connections object feeding it.
Trap 2: useDataOfInput is 1-indexed, connections are 0-indexed
Symptom: the Merge passes through the wrong source. Downstream gets real data with real field names — just from the wrong upstream branch. Looks identical to a working flow; the shape is right, the contents are wrong.
Cause: parameters.useDataOfInput matches the UI labels (Input 1, Input 2, Input 3 — 1-indexed), but the wiring position in connections.<source>.main[idx] is 0-indexed like every other array. Off by one.
Fix — the translation rule:
useDataOfInput: "N"is fed by the connection atmain[N-1].
useDataOfInput | Connection slot |
|---|---|
"1" | connections.<source>.main[0] |
"2" | connections.<source>.main[1] |
"3" | connections.<source>.main[2] |
When you add the connection via n8n_update_partial_workflow, the addConnection operation targets a specific input index. To pass through Input 2, the source whose data you want must land on the connection at main[1]. After wiring, verify with `n8n_get_workflow`: read the connections object and confirm the source you intend to pass through actually sits at main[N-1]. This is the only reliable check — it won't surface in validation.
---
Database
Covers Postgres, MySQL, and Supabase (when used via the Postgres node against the same database). The exact field set differs per node and version — get_node is canonical. This is the security and behavior layer it doesn't show.
Never interpolate user input into SQL
Symptom: the query works in testing, then a value containing a quote or ; produces a SQL error — or worse, executes injected SQL. $json.email = "x'; DROP TABLE users; --" is game over.
Cause: n8n substitutes {{ ... }} expressions into the query text before the database driver binds parameters. Anything inside {{ }} becomes part of the SQL itself, not a bound value.
Fix: use $1, $2, ... placeholders in the query and pass values through options.queryReplacement. The values flow through the driver's parameter binding and never touch the SQL text. (The n8n MySQL node also uses $1, $2 + queryReplacement, not MySQL's native ? — the node normalizes to the driver.)
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM users WHERE email = $1",
"options": {
"queryReplacement": "={{ $json.email }}"
}
}
}queryReplacement takes a comma-separated list — each piece becomes one parameter: ={{ $json.email }},={{ $json.id }} → $1, $2. The = prefix is just n8n's expression-mode marker. Treat any DB node with a {{ ... }} expression inside parameters.query as a critical injection finding.
Transactions are bounded to one node
Symptom: two separate DB nodes, the second fails, and the first's write is already committed — no rollback.
Cause: there is no cross-node transaction in n8n. Atomicity is bounded to a single executeQuery invocation.
Fix: for atomic multi-step writes, put all the statements in one Postgres/MySQL executeQuery node and set options.queryBatching: "transaction" explicitly — don't rely on the default, which has shifted across node versions (single-query and independent batching are the other modes; confirm the current set and default with get_node). Everything that node runs in that execution goes through one BEGIN/COMMIT; any failure rolls it all back. Pre-compute lookups and derived values upstream so the transactional node receives ready-to-write data.
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO orders (customer_id, total) VALUES ($1, $2)",
"options": {
"queryBatching": "transaction",
"queryReplacement": "={{ $json.customerId }},={{ $json.total }}"
}
}
}Supabase's REST layer has no transactions — drop to the Postgres node connected directly to the same database when you need atomicity.
"No rows" produces no items
Symptom: a select / executeQuery that matches nothing returns zero items, and the downstream node simply doesn't run — looks like the branch was skipped.
Cause: zero matched rows = zero n8n output items, and most nodes treat "no input items" as "nothing to do."
Fix: set alwaysOutputData: true on the DB node so a single empty item flows through, then branch on the result with an IF. (This is the same gotcha as write operations — INSERT/UPDATE/DELETE often return 0 items too; alwaysOutputData: true keeps the chain alive.)
---
Slack
The exact param shapes shift across versions — get_node for nodes-base.slack is canonical. These are the traps it won't warn you about.
Block Kit must be wrapped, or it posts as plain text
Symptom: you pass a Block Kit array, the request succeeds, but the message arrives as plain text (or empty). No node error, no validation warning.
Cause: the node accepts a bare array silently and drops the rich content. Slack's chat.postMessage expects { "blocks": [...] } — an object with a blocks key — and the node forwards your value as-is.
Fix: wrap the array in an object, in expression mode so the node receives a real object (not a stringified one). Reference the source by node name, not $json:
={{ { "blocks": $('Build Message').item.json.blocks } }}Don't stringify-then-reparse hybrids ({{ ... .toJsonString() }} glued into a string) — they work on some versions but break on escaping and large payloads. Hand the node the structure directly.
Thread replies need thread_ts
Symptom: a "reply" posts as a new top-level channel message instead of in the thread.
Cause: without thread_ts (the timestamp of the message being replied to), Slack has no thread to attach to.
Fix: set thread_ts to the parent message's ts. Use get_node to find where the field sits on the current version — it moved out of otherOptions where older docs put it. Add reply_broadcast: true if the reply should also show in the main channel.
Operation display name ≠ internal value
Symptom: you set operation: "send" (matching the UI's "Send a message") and validation rejects it.
Cause: the display label and the stored value diverge. "Send a message" is operation: "post", not "send".
Fix: read the real operation values from get_node for nodes-base.slack rather than guessing from the UI label. This display-vs-value mismatch recurs across resource nodes (e.g. "Get Many" → getAll on Gmail/Supabase).
---
Webhook / Respond to Webhook
Entry and exit of request/response API workflows. get_node is canonical for field shapes; this is the runtime behavior it doesn't show.
Response code defaults to 200 — even on error branches
Symptom: an error branch returns HTTP 200 with an error body. The caller's HTTP client sees success while the body says failure — the worst of both worlds, because the caller's error handling never fires.
Cause: responseCode defaults to 200 on every Respond to Webhook node, including the ones you wired to error paths.
Fix: set responseCode explicitly on every Respond branch — 4xx for caller errors (400 validation, 401/403 auth, 409 conflict, 429 rate limit), 5xx for server errors. A workflow can have multiple Respond nodes, one per response shape; n8n returns whichever fires first.
Use responseMode: "responseNode" for real request/response APIs
Symptom: the caller gets an immediate 200 and never sees the workflow's actual output, even though the workflow computes a response.
Cause: the Webhook trigger's responseMode defaults to onReceived (acknowledge immediately, run async). The caller can't see downstream results.
Fix: set parameters.responseMode: "responseNode" on the Webhook trigger and control the response with explicit Respond to Webhook nodes. (lastNode returns the last node's output synchronously — fine for simple cases; responseNode is the flexible choice for multi-status APIs.)
respondWith: "json" takes the object, not a stringified string
Symptom: the response body comes back double-encoded — escaped quotes, a JSON string wrapped in another JSON string.
Cause: the responseBody field accepts both an object and a string. If you pass JSON.stringify(obj), n8n serializes that string again.
Fix: pass the object directly in expression mode and let the node serialize it once:
={{ { "status": "ok", "id": $('Create Record').item.json.id } }}---
Schedule Trigger
get_node for nodes-base.scheduleTrigger shows the rule structure. These are the behaviors outside the type def.
Timezone is workflow-level, not per-rule
Symptom: a job that should fire at 9am local drifts after a DST change or an instance move.
Cause: the Schedule Trigger uses the workflow's timezone (Workflow Settings → Timezone). There is no timezone field inside a rule. Without an explicit workflow timezone, it follows the host's clock.
Fix: set the workflow timezone explicitly for any schedule that must run at a specific local time. The per-rule config has no timezone to set — don't look for one.
Cron accepts 5 or 6 fields
Symptom: confusion over whether a cron expression needs a seconds field — the UI hint shows 6 fields, the placeholder shows 5.
Cause: n8n's cron supports both 5-field (Minute Hour DoM Month DoW) and 6-field (Second Minute Hour DoM Month DoW) formats. Both are valid.
Fix: use whichever you intend; just be consistent. For simple recurrences ("every Monday 9am"), the interval modes (field: "weeks" etc.) are clearer and less error-prone than cron.
Restarts can miss runs — design for idempotency
Symptom: an instance restart or downtime window overlapping a scheduled time, and that run never happens.
Cause: schedules fire against the instance's clock. If the instance is down at fire time, the run is simply skipped — there's no catch-up queue.
Fix: for business-critical schedules, make the workflow idempotent (running it twice produces the same result) and, where it matters, detect missed runs at workflow start by comparing the last successful run to the expected cadence and catching up.
Operation Patterns Guide
Common node configuration patterns organized by node type and operation.
---
Overview
Purpose: Quick reference for common node configurations
Coverage: Top 20 most-used nodes from 525 available
Pattern format:
- Minimal valid configuration
- Common options
- Real-world examples
- Gotchas and tips
---
HTTP & API Nodes
HTTP Request (nodes-base.httpRequest)
Most versatile node for HTTP operations
GET Request
Minimal:
{
"method": "GET",
"url": "https://api.example.com/users",
"authentication": "none"
}With query parameters:
{
"method": "GET",
"url": "https://api.example.com/users",
"authentication": "none",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "limit",
"value": "100"
},
{
"name": "offset",
"value": "={{$json.offset}}"
}
]
}
}With authentication:
{
"method": "GET",
"url": "https://api.example.com/users",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth"
}POST with JSON
Minimal:
{
"method": "POST",
"url": "https://api.example.com/users",
"authentication": "none",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"name": "John Doe",
"email": "john@example.com"
}
}
}With expressions:
{
"method": "POST",
"url": "https://api.example.com/users",
"authentication": "none",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"name": "={{$json.name}}",
"email": "={{$json.email}}",
"metadata": {
"source": "n8n",
"timestamp": "={{$now.toISO()}}"
}
}
}
}Gotcha: Remember sendBody: true for POST/PUT/PATCH!
PUT/PATCH Request
Pattern: Same as POST, but method changes
{
"method": "PUT", // or "PATCH"
"url": "https://api.example.com/users/123",
"authentication": "none",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"name": "Updated Name"
}
}
}DELETE Request
Minimal (no body):
{
"method": "DELETE",
"url": "https://api.example.com/users/123",
"authentication": "none"
}With body (some APIs allow):
{
"method": "DELETE",
"url": "https://api.example.com/users",
"authentication": "none",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"ids": ["123", "456"]
}
}
}---
Webhook (nodes-base.webhook)
Most common trigger - 813 searches!
Basic Webhook
Minimal:
{
"path": "my-webhook",
"httpMethod": "POST",
"responseMode": "onReceived"
}Gotcha: Webhook data is under $json.body, not $json!
// ❌ Wrong
{
"text": "={{$json.email}}"
}
// ✅ Correct
{
"text": "={{$json.body.email}}"
}Webhook with Authentication
Header auth:
{
"path": "secure-webhook",
"httpMethod": "POST",
"responseMode": "onReceived",
"authentication": "headerAuth",
"options": {
"responseCode": 200,
"responseData": "{\n \"success\": true\n}"
}
}Webhook Returning Data
Custom response:
{
"path": "my-webhook",
"httpMethod": "POST",
"responseMode": "lastNode", // Return data from last node
"options": {
"responseCode": 201,
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
}
}---
Communication Nodes
Slack (nodes-base.slack)
Popular choice for AI agent workflows
Post Message
Minimal:
{
"resource": "message",
"operation": "post",
"channel": "#general",
"text": "Hello from n8n!"
}With dynamic content:
{
"resource": "message",
"operation": "post",
"channel": "={{$json.channel}}",
"text": "New user: {{$json.name}} ({{$json.email}})"
}With attachments:
{
"resource": "message",
"operation": "post",
"channel": "#alerts",
"text": "Error Alert",
"attachments": [
{
"color": "#ff0000",
"fields": [
{
"title": "Error Type",
"value": "={{$json.errorType}}"
},
{
"title": "Timestamp",
"value": "={{$now.toLocaleString()}}"
}
]
}
]
}Gotcha: Channel must start with # for public channels or be a channel ID!
Update Message
Minimal:
{
"resource": "message",
"operation": "update",
"messageId": "1234567890.123456", // From previous message post
"text": "Updated message content"
}Note: messageId required, channel optional (can be inferred)
Create Channel
Minimal:
{
"resource": "channel",
"operation": "create",
"name": "new-project-channel", // Lowercase, no spaces
"isPrivate": false
}Gotcha: Channel name must be lowercase, no spaces, 1-80 chars!
---
Gmail (nodes-base.gmail)
Popular for email automation
Send Email
Minimal:
{
"resource": "message",
"operation": "send",
"to": "user@example.com",
"subject": "Hello from n8n",
"message": "This is the email body"
}With dynamic content:
{
"resource": "message",
"operation": "send",
"to": "={{$json.email}}",
"subject": "Order Confirmation #{{$json.orderId}}",
"message": "Dear {{$json.name}},\n\nYour order has been confirmed.\n\nThank you!",
"options": {
"ccList": "admin@example.com",
"replyTo": "support@example.com"
}
}Get Email
Minimal:
{
"resource": "message",
"operation": "getAll",
"returnAll": false,
"limit": 10
}With filters:
{
"resource": "message",
"operation": "getAll",
"returnAll": false,
"limit": 50,
"filters": {
"q": "is:unread from:important@example.com",
"labelIds": ["INBOX"]
}
}---
Database Nodes
Postgres (nodes-base.postgres)
Database operations - 456 templates
Execute Query
Minimal (SELECT):
{
"operation": "executeQuery",
"query": "SELECT * FROM users WHERE active = true LIMIT 100"
}With parameters (SQL injection prevention):
{
"operation": "executeQuery",
"query": "SELECT * FROM users WHERE email = $1 AND active = $2",
"additionalFields": {
"mode": "list",
"queryParameters": "user@example.com,true"
}
}Gotcha: ALWAYS use parameterized queries for user input!
// ❌ BAD - SQL injection risk!
{
"query": "SELECT * FROM users WHERE email = '{{$json.email}}'"
}
// ✅ GOOD - Parameterized
{
"query": "SELECT * FROM users WHERE email = $1",
"additionalFields": {
"mode": "list",
"queryParameters": "={{$json.email}}"
}
}Insert
Minimal:
{
"operation": "insert",
"table": "users",
"columns": "name,email,created_at",
"additionalFields": {
"mode": "list",
"queryParameters": "John Doe,john@example.com,NOW()"
}
}With expressions:
{
"operation": "insert",
"table": "users",
"columns": "name,email,metadata",
"additionalFields": {
"mode": "list",
"queryParameters": "={{$json.name}},={{$json.email}},{{JSON.stringify($json)}}"
}
}Update
Minimal:
{
"operation": "update",
"table": "users",
"updateKey": "id",
"columns": "name,email",
"additionalFields": {
"mode": "list",
"queryParameters": "={{$json.id}},Updated Name,newemail@example.com"
}
}---
Storage Nodes
Data Table (nodes-base.dataTable)
Persistent, structured per-project key-value storage — an in-n8n alternative to external SQL for small state like buffers, de-dup sets, counters, or lookup caches. Do not confuse with the MCP tool `n8n_manage_datatable` — that tool manages tables from outside n8n (create/list/delete tables and rows from Claude). The `nodes-base.dataTable` node below is what you drop into a workflow to read/write rows during execution.
Verified end-to-end against live n8n on 2026-04-08 with a 15-node assertion harness exercising every claim below: insert returning rows with systemid,likeoperator,returnAll,allConditionsAND-of-multiple-filters,isTrueunary boolean condition,upsertwithmatchingColumns(no duplicates),defineBelowresourceMapper writing values,deleteRows(the reserved-word workaround) returning affected rows, anddataTableIdresourceLocator inmode: "name". All 6 assertions passed.
Node shape:
type:n8n-nodes-base.dataTabletypeVersion:1.1(also1)resource:"row"or"table"- Row
operationvalues — note the reserved-word workaround on delete: "insert"— Insert row"get"— Get row(s)"update"— Update row(s) matching conditions"upsert"— Update if match, else insert"deleteRows"— Delete row(s) matching conditions (not `"delete"` —deleteis a JS reserved word, the node usesdeleteRows)"rowExists"— Pass through input if at least one match"rowNotExists"— Pass through input if zero matches
Table selection — always a resourceLocator parameter named dataTableId:
"dataTableId": {
"__rl": true,
"mode": "list", // or "name" or "id"
"value": "dt_xyz123" // or the name when mode=name
}Row mapping (insert/update/upsert) — resourceMapper parameter named columns:
"columns": {
"mappingMode": "defineBelow", // or "autoMapInputData"
"value": {
"user_email": "={{ $json.email }}",
"score": "={{ $json.score }}",
"active": true
},
"matchingColumns": [], // filled for update/upsert match keys
"schema": [], // n8n re-loads at runtime; safe to leave empty
"attemptToConvertTypes": false,
"convertFieldsToString": false
}In autoMapInputData mode, incoming item field names must match column names exactly and value is ignored.
Filtering (get/update/upsert/deleteRows/rowExists/rowNotExists):
"matchType": "anyCondition", // or "allConditions"
"filters": {
"conditions": [
{ "keyName": "user_email", "condition": "eq", "keyValue": "a@b.com" },
{ "keyName": "score", "condition": "gte", "keyValue": 10 },
{ "keyName": "archived", "condition": "isNotEmpty" }
]
}Supported condition values: eq, neq, like, ilike, gt, gte, lt, lte, isEmpty, isNotEmpty, isTrue, isFalse. The last four are unary — omit keyValue.
Get options: returnAll: true bypasses the default 50-row limit. options can include ordering.
Insert option: options.optimizeBulk: true skips returning inserted rows for ~5x bulk throughput. Do not use when downstream nodes need the inserted row ids.
Mutating ops (update/upsert/deleteRows) accept options.dryRun: true — returns the rows that would be affected with before/after states, without writing.
Minimal Insert
{
"resource": "row",
"operation": "insert",
"dataTableId": { "__rl": true, "mode": "name", "value": "email_buffer" },
"columns": {
"mappingMode": "defineBelow",
"value": {
"from_name": "={{ $json.from_name }}",
"subject": "={{ $json.subject }}"
},
"matchingColumns": [],
"schema": []
},
"options": {}
}Get All Rows
Get requires at least one condition — a bare "return everything" isn't allowed. Trick: filter on the always-populated system id column with isNotEmpty.
{
"resource": "row",
"operation": "get",
"dataTableId": { "__rl": true, "mode": "name", "value": "email_buffer" },
"matchType": "anyCondition",
"filters": {
"conditions": [ { "keyName": "id", "condition": "isNotEmpty" } ]
},
"returnAll": true,
"options": {}
}Delete All Rows
Same id isNotEmpty trick — a delete without conditions throws At least one condition is required.
{
"resource": "row",
"operation": "deleteRows",
"dataTableId": { "__rl": true, "mode": "name", "value": "email_buffer" },
"matchType": "anyCondition",
"filters": {
"conditions": [ { "keyName": "id", "condition": "isNotEmpty" } ]
},
"options": {}
}Upsert by Natural Key
{
"resource": "row",
"operation": "upsert",
"dataTableId": { "__rl": true, "mode": "name", "value": "user_scores" },
"matchType": "allConditions",
"filters": {
"conditions": [ { "keyName": "user_email", "condition": "eq", "keyValue": "={{ $json.email }}" } ]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"user_email": "={{ $json.email }}",
"score": "={{ $json.score }}"
},
"matchingColumns": ["user_email"],
"schema": []
},
"options": {}
}System columns: every table auto-has id plus created/updated timestamps — you don't declare these and can't write to them. They're usable in filters.
When to reach for Data Table vs alternatives:
| Need | Use |
|---|---|
| Small per-workflow scratch state, single workflow, not durable across workflow edits | $getWorkflowStaticData('global') inside a Code node |
| Persistent structured state, queryable by column, survives workflow rename/edit/deactivation, shared across multiple workflows in the same project | Data Table node |
| Large datasets (>>10k rows), complex joins, transactions, FKs, indexes | External Postgres/MySQL |
| Unstructured key-value cache with TTL | Redis |
Gotchas:
- Scope is per project — Data Tables are not shared across n8n projects. Move a workflow to another project and its Data Table references break.
- Filter operator
eqon a column that doesn't exist in the table returns a validation error at execution, not at import — always verify column names match the live table. - Expression values in
columns.valueare evaluated per input item. If the upstream node emits N items, Insert runs N times unless you explicitly useoptimizeBulk. deleteRowsis the operation value, notdelete. Usingdeletewill import but fail at execution with "unknown operation".- Race condition in buffer/flush patterns: rows added between
GetanddeleteRowswill be wiped without being read. For at-least-once semantics, delete by specific row ids returned fromGetinstead of by a broad filter. - Zero-match halts the chain. When
get,deleteRows,update, orupsertmatches 0 rows, the node emits 0 output items and n8n stops the downstream branch silently — no error, just nothing happens. This bites cleanup steps in idempotent test/setup workflows where the table starts empty. Fix: set node-level"alwaysOutputData": true(sibling ofparameters/type, NOT insideparameters) on any DT node that may legitimately match nothing. The node will then emit a single empty item and the chain continues. - DT operations execute once per input item. A
Getnode fed 3 input items will run 3 separate queries and concatenate the results — usually not what you want. Insert a "collapse" Code node (return [{ json: {} }];) between any multi-item-emitting node and a downstream DT op that should run exactly once. - DT nodes do not natively offer a "run once for all items" mode like the Code node — the collapse-node pattern is currently the only clean workaround.
---
Data Transformation Nodes
Set (nodes-base.set)
Most used transformation - 68% of workflows!
Set Fixed Values
Minimal:
{
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"name": "status",
"value": "active",
"type": "string"
},
{
"name": "count",
"value": 100,
"type": "number"
}
]
}
}Set from Input Data
Mapping data:
{
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"name": "fullName",
"value": "={{$json.firstName}} {{$json.lastName}}",
"type": "string"
},
{
"name": "email",
"value": "={{$json.email.toLowerCase()}}",
"type": "string"
},
{
"name": "timestamp",
"value": "={{$now.toISO()}}",
"type": "string"
}
]
}
}Gotcha: Use correct type for each field!
// ❌ Wrong type
{
"name": "age",
"value": "25", // String
"type": "string" // Will be string "25"
}
// ✅ Correct type
{
"name": "age",
"value": 25, // Number
"type": "number" // Will be number 25
}---
Code (nodes-base.code)
JavaScript execution - 42% of workflows
Simple Transformation
Minimal:
{
"mode": "runOnceForAllItems",
"jsCode": "return $input.all().map(item => ({\n json: {\n name: item.json.name.toUpperCase(),\n email: item.json.email\n }\n}));"
}Per-item processing:
{
"mode": "runOnceForEachItem",
"jsCode": "// Process each item\nconst data = $input.item.json;\n\nreturn {\n json: {\n fullName: `${data.firstName} ${data.lastName}`,\n email: data.email.toLowerCase(),\n timestamp: new Date().toISOString()\n }\n};"
}Gotcha: In Code nodes, use $input.item.json or $input.all(), NOT {{...}}!
// ❌ Wrong - expressions don't work in Code nodes
{
"jsCode": "const name = '={{$json.name}}';"
}
// ✅ Correct - direct access
{
"jsCode": "const name = $input.item.json.name;"
}---
Conditional Nodes
IF (nodes-base.if)
Conditional logic - 38% of workflows
String Comparison
Equals (binary):
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "active"
}
]
}
}Contains (binary):
{
"conditions": {
"string": [
{
"value1": "={{$json.email}}",
"operation": "contains",
"value2": "@example.com"
}
]
}
}isEmpty (unary):
{
"conditions": {
"string": [
{
"value1": "={{$json.email}}",
"operation": "isEmpty"
// No value2 - unary operator
// singleValue: true added by auto-sanitization
}
]
}
}Gotcha: Unary operators (isEmpty, isNotEmpty) don't need value2!
Number Comparison
Greater than:
{
"conditions": {
"number": [
{
"value1": "={{$json.age}}",
"operation": "larger",
"value2": 18
}
]
}
}Boolean Comparison
Is true:
{
"conditions": {
"boolean": [
{
"value1": "={{$json.isActive}}",
"operation": "true"
// Unary - no value2
}
]
}
}Multiple Conditions (AND)
All must match:
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "active"
}
],
"number": [
{
"value1": "={{$json.age}}",
"operation": "larger",
"value2": 18
}
]
},
"combineOperation": "all" // AND logic
}Multiple Conditions (OR)
Any can match:
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "active"
},
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "pending"
}
]
},
"combineOperation": "any" // OR logic
}---
Switch (nodes-base.switch)
Multi-way routing - 18% of workflows
Basic Switch
Minimal:
{
"mode": "rules",
"rules": {
"rules": [
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "active"
}
]
}
},
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "pending"
}
]
}
}
]
},
"fallbackOutput": "extra" // Catch-all for non-matching
}Gotcha: Number of rules must match number of outputs!
---
AI Nodes
OpenAI (nodes-langchain.openAi)
AI operations - 234 templates
Chat Completion
Minimal:
{
"resource": "chat",
"operation": "complete",
"messages": {
"values": [
{
"role": "user",
"content": "={{$json.prompt}}"
}
]
}
}With system prompt:
{
"resource": "chat",
"operation": "complete",
"messages": {
"values": [
{
"role": "system",
"content": "You are a helpful assistant specialized in customer support."
},
{
"role": "user",
"content": "={{$json.userMessage}}"
}
]
},
"options": {
"temperature": 0.7,
"maxTokens": 500
}
}---
Schedule Nodes
Schedule Trigger (nodes-base.scheduleTrigger)
Time-based workflows - 28% have schedule triggers
Daily at Specific Time
Minimal:
{
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 24
}
],
"hour": 9,
"minute": 0,
"timezone": "America/New_York"
}
}Gotcha: Always set timezone explicitly!
// ❌ Bad - uses server timezone
{
"rule": {
"interval": [...]
}
}
// ✅ Good - explicit timezone
{
"rule": {
"interval": [...],
"timezone": "America/New_York"
}
}Every N Minutes
Minimal:
{
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
}Cron Expression
Advanced scheduling:
{
"mode": "cron",
"cronExpression": "0 */2 * * *", // Every 2 hours
"timezone": "America/New_York"
}---
Summary
Key Patterns by Category:
| Category | Most Common | Key Gotcha |
|---|---|---|
| HTTP/API | GET, POST JSON | Remember sendBody: true |
| Webhooks | POST receiver | Data under $json.body |
| Communication | Slack post | Channel format (#name) |
| Database | SELECT with params | Use parameterized queries |
| Transform | Set assignments | Correct type per field |
| Conditional | IF string equals | Unary vs binary operators |
| AI | OpenAI chat | System + user messages |
| Schedule | Daily at time | Set timezone explicitly |
Configuration Approach: 1. Use patterns as starting point 2. Adapt to your use case 3. Validate configuration 4. Iterate based on errors 5. Deploy when valid
Related Files:
- [SKILL.md](SKILL.md) - Configuration workflow and philosophy
- [DEPENDENCIES.md](DEPENDENCIES.md) - Property dependency rules
---
Worked Example: Configuring HTTP Request Step by Step
A full validate-driven walkthrough of building a POST JSON request from minimal config, letting validation surface each required field.
Step 1: Identify what you need
// Goal: POST JSON to APIStep 2: Get node info
const info = get_node({
nodeType: "nodes-base.httpRequest"
});
// Returns: method, url, sendBody, body, authentication required/optionalStep 3: Minimal config
{
"method": "POST",
"url": "https://api.example.com/create",
"authentication": "none"
}Step 4: Validate
validate_node({
nodeType: "nodes-base.httpRequest",
config,
profile: "runtime"
});
// → Error: "sendBody required for POST"Step 5: Add required field
{
"method": "POST",
"url": "https://api.example.com/create",
"authentication": "none",
"sendBody": true
}Step 6: Validate again
validate_node({...});
// → Error: "body required when sendBody=true"Step 7: Complete configuration
{
"method": "POST",
"url": "https://api.example.com/create",
"authentication": "none",
"sendBody": true,
"body": {
"contentType": "json",
"content": {
"name": "={{$json.name}}",
"email": "={{$json.email}}"
}
}
}Step 8: Final validation
validate_node({...});
// → Valid! ✅---
Operation-Specific Configuration Examples
Concrete minimal configs showing how required fields differ by resource + operation.
Slack Node Examples
Post Message
{
"resource": "message",
"operation": "post",
"channel": "#general", // Required
"text": "Hello!", // Required
"attachments": [], // Optional
"blocks": [] // Optional
}Update Message
{
"resource": "message",
"operation": "update",
"messageId": "1234567890", // Required (different from post!)
"text": "Updated!", // Required
"channel": "#general" // Optional (can be inferred)
}Create Channel
{
"resource": "channel",
"operation": "create",
"name": "new-channel", // Required
"isPrivate": false // Optional
// Note: text NOT required for this operation
}HTTP Request Node Examples
GET Request
{
"method": "GET",
"url": "https://api.example.com/users",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"sendQuery": true, // Optional
"queryParameters": { // Shows when sendQuery=true
"parameters": [
{
"name": "limit",
"value": "100"
}
]
}
}POST with JSON
{
"method": "POST",
"url": "https://api.example.com/users",
"authentication": "none",
"sendBody": true, // Required for POST
"body": { // Required when sendBody=true
"contentType": "json",
"content": {
"name": "John Doe",
"email": "john@example.com"
}
}
}IF Node Examples
String Comparison (Binary)
{
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equals",
"value2": "active" // Binary: needs value2
}
]
}
}Empty Check (Unary)
{
"conditions": {
"string": [
{
"value1": "={{$json.email}}",
"operation": "isEmpty",
// No value2 - unary operator
"singleValue": true // Auto-added by sanitization
}
]
}
}n8n Node Configuration
Expert guidance for operation-aware node configuration with property dependencies.
Overview
Skill Name: n8n Node Configuration Priority: Medium Purpose: Teach operation-aware configuration with progressive discovery and dependency awareness
The Problem This Solves
Node configuration patterns:
- get_node is the primary discovery tool (18s avg from search → standard detail)
- 91.7% success rate with standard detail configuration
- 56 seconds average between configuration edits
Key insight: Most configurations only need standard detail, not full schema!
What This Skill Teaches
Core Concepts
1. Operation-Aware Configuration
- Resource + operation determine required fields
- Different operations = different requirements
- Always check requirements when changing operation
2. Property Dependencies
- Fields appear/disappear based on other field values
- displayOptions control visibility
- Conditional required fields
- Understanding dependency chains
3. Progressive Discovery
- Start with get_node standard detail (91.7% success)
- Escalate to get_node({mode: "search_properties"}) if needed
- Use get_node({detail: "full"}) only when necessary
- Right tool for right job
4. Configuration Workflow
- Identify → Discover → Configure → Validate → Iterate
- Average 2-3 validation cycles
- Read errors for dependency hints
- 56 seconds between edits average
5. Common Patterns
- Resource/operation nodes (Slack, Sheets)
- HTTP-based nodes (HTTP Request, Webhook)
- Database nodes (Postgres, MySQL)
- Conditional logic nodes (IF, Switch)
File Structure
n8n-node-configuration/
├── SKILL.md
│ Main configuration guide
│ - Configuration philosophy (progressive disclosure)
│ - Core concepts (operation-aware, dependencies)
│ - Configuration workflow (8-step process)
│ - get_node vs get_node({detail: "full"})
│ - Property dependencies deep dive
│ - Common node patterns (4 categories)
│ - Operation-specific examples
│ - Conditional requirements
│ - Anti-patterns and best practices
│
├── DEPENDENCIES.md
│ Property dependencies reference
│ - displayOptions mechanism
│ - show vs hide rules
│ - Multiple conditions (AND logic)
│ - Multiple values (OR logic)
│ - 4 common dependency patterns
│ - Using get_node({mode: "search_properties"})
│ - Complex dependency examples
│ - Nested dependencies
│ - Auto-sanitization interaction
│ - Troubleshooting guide
│ - Advanced patterns
│
├── OPERATION_PATTERNS.md
│ Common configurations by node type
│ - HTTP Request (GET/POST/PUT/DELETE)
│ - Webhook (basic/auth/response)
│ - Slack (post/update/create)
│ - Gmail (send/get)
│ - Postgres (query/insert/update)
│ - Set (values/mapping)
│ - Code (per-item/all-items)
│ - IF (string/number/boolean)
│ - Switch (rules/fallback)
│ - OpenAI (chat completion)
│ - Schedule (daily/interval/cron)
│ - Gotchas and tips for each
│
└── README.md (this file)
Skill metadata and statisticsTotal: 4 files + 4 evaluations
Usage Statistics
Configuration metrics:
| Metric | Value | Insight |
|---|---|---|
| get_node | Primary tool | Most popular discovery pattern |
| Success rate (standard) | 91.7% | Standard detail sufficient for most |
| Avg time search→get_node | 18 seconds | Fast discovery workflow |
| Avg time between edits | 56 seconds | Iterative configuration |
Tool Usage Pattern
Most common discovery pattern:
search_nodes → get_node (18s average)Configuration cycle:
get_node → configure → validate → iterate (56s avg per edit)Key Insights
1. Progressive Disclosure Works
91.7% success rate with get_node (standard detail) proves most configurations don't need full schema.
Strategy: 1. Start with standard detail 2. Escalate to search_properties mode if stuck 3. Use full schema only when necessary
2. Operations Determine Requirements
Same node, different operation = different requirements
Example: Slack message
operation="post"→ needs channel + textoperation="update"→ needs messageId + text (different!)
3. Dependencies Control Visibility
Fields appear/disappear based on other values
Example: HTTP Request
method="GET"→ body hiddenmethod="POST"+sendBody=true→ body required
4. Configuration is Iterative
Average 56 seconds between edits shows configuration is iterative, not one-shot.
Normal workflow: 1. Configure minimal 2. Validate → error 3. Add missing field 4. Validate → error 5. Adjust value 6. Validate → valid ✅
5. Common Gotchas Exist
Top 5 gotchas from patterns: 1. Webhook data under $json.body (not $json) 2. POST needs sendBody: true 3. Slack channel format (#name) 4. SQL parameterized queries (injection prevention) 5. Timezone must be explicit (schedule nodes)
Usage Examples
Example 1: Basic Configuration Flow
// Step 1: Get standard info
const info = get_node({
nodeType: "nodes-base.slack"
});
// Step 2: Configure for operation
{
"resource": "message",
"operation": "post",
"channel": "#general",
"text": "Hello!"
}
// Step 3: Validate
validate_node({...});
// ✅ Valid!Example 2: Handling Dependencies
// Step 1: Configure HTTP POST
{
"method": "POST",
"url": "https://api.example.com/create"
}
// Step 2: Validate → Error: "sendBody required"
// Step 3: Check dependencies
get_node({mode: "search_properties"})({
nodeType: "nodes-base.httpRequest"
});
// Shows: body visible when sendBody=true
// Step 4: Fix
{
"method": "POST",
"url": "https://api.example.com/create",
"sendBody": true,
"body": {
"contentType": "json",
"content": {...}
}
}
// ✅ Valid!Example 3: Operation Change
// Initial config (post operation)
{
"resource": "message",
"operation": "post",
"channel": "#general",
"text": "Hello"
}
// Change operation
{
"resource": "message",
"operation": "update", // Changed!
// Need to check new requirements
}
// Get standard info for update operation
get_node({nodeType: "nodes-base.slack"});
// Shows: messageId required, channel optional
// Correct config
{
"resource": "message",
"operation": "update",
"messageId": "1234567890.123456",
"text": "Updated"
}When This Skill Activates
Trigger phrases:
- "how to configure"
- "what fields are required"
- "property dependencies"
- "get_node vs get_node({detail: "full"})"
- "operation-specific"
- "field not visible"
Common scenarios:
- Configuring new nodes
- Understanding required fields
- Field appears/disappears unexpectedly
- Choosing between discovery tools
- Switching operations
- Learning common patterns
Integration with Other Skills
Works With:
- n8n MCP Tools Expert - How to call discovery tools correctly
- n8n Validation Expert - Interpret missing_required errors
- n8n Expression Syntax - Configure expression fields
- n8n Workflow Patterns - Apply patterns with proper node config
Complementary:
- Use MCP Tools Expert to learn tool selection
- Use Validation Expert to fix configuration errors
- Use Expression Syntax for dynamic field values
- Use Workflow Patterns to understand node relationships
Testing
Evaluations: 4 test scenarios
1. eval-001-property-dependencies.json
- Tests understanding of displayOptions
- Guides to get_node({mode: "search_properties"})
- Explains conditional requirements
2. eval-002-operation-specific-config.json
- Tests operation-aware configuration
- Identifies resource + operation pattern
- References OPERATION_PATTERNS.md
3. eval-003-conditional-fields.json
- Tests unary vs binary operators
- Explains singleValue dependency
- Mentions auto-sanitization
4. eval-004-standard-vs-full.json
- Tests tool selection knowledge
- Explains progressive disclosure
- Provides success rate statistics
Success Metrics
Before this skill:
- Using get_node({detail: "full"}) for everything (slow, overwhelming)
- Not understanding property dependencies
- Confused when fields appear/disappear
- Not aware of operation-specific requirements
- Trial and error configuration
After this skill:
- Start with get_node standard detail (91.7% success)
- Understand displayOptions mechanism
- Predict field visibility based on dependencies
- Check requirements when changing operations
- Systematic configuration approach
- Know common patterns by node type
Coverage
Node types covered: Top 20 most-used nodes
| Category | Nodes | Coverage |
|---|---|---|
| HTTP/API | HTTP Request, Webhook | Complete |
| Communication | Slack, Gmail | Common operations |
| Database | Postgres, MySQL | CRUD operations |
| Transform | Set, Code | All modes |
| Conditional | IF, Switch | All operator types |
| AI | OpenAI | Chat completion |
| Schedule | Schedule Trigger | All modes |
Related Documentation
- n8n-mcp MCP Server: Provides discovery tools
- n8n Node API: get_node, get_node({mode: "search_properties"}), get_node({detail: "full"})
- n8n Schema: displayOptions mechanism, property definitions
Version History
- v1.0 (2025-10-20): Initial implementation
- SKILL.md with configuration workflow
- DEPENDENCIES.md with displayOptions deep dive
- OPERATION_PATTERNS.md with 20+ node patterns
- 4 evaluation scenarios
Author
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Part of the n8n-skills meta-skill collection.
Related skills
How it compares
Use this skill while authoring node schemas; use general n8n workflow skills when wiring triggers and actions in existing nodes.
FAQ
When should I use get_node with detail='standard' vs detail='full'?
Start with standard (default) - it covers 95% of cases with ~1-2K tokens. Only escalate to full if standard doesn't show a field you need, or use search_properties mode to find specific fields by name.
Why does my required field disappear when I change the operation?
Different operations have different required fields. Slack post requires channel+text, but update requires messageId+text. Always call get_node after changing operation to see new requirements.
How do I know which fields control visibility of other fields?
Use displayOptions logic: fields show when conditions are met (e.g. body shows when sendBody=true AND method IN POST/PUT/PATCH). Search for the field with get_node({mode:'search_properties'}) or use detail='full' to see its displayOptions rules.
Is N8n Node Configuration safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.