Claude Code Plugins

Community-maintained marketplace

Feedback

n8n-expression-testing

@pacphi/ampel
1
0

n8n expression syntax validation, context-aware testing, common pitfalls detection, and performance optimization. Use when validating n8n expressions and data transformations.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name n8n-expression-testing
description n8n expression syntax validation, context-aware testing, common pitfalls detection, and performance optimization. Use when validating n8n expressions and data transformations.
category n8n-testing
priority high
tokenEstimate 1000
agents n8n-expression-validator
implementation_status production
optimization_version 1
last_optimized Mon Dec 15 2025 00:00:00 GMT+0000 (Coordinated Universal Time)
dependencies
quick_reference_card true
tags n8n, expressions, javascript, data-transformation, validation

n8n Expression Testing

When testing n8n expressions:
  1. VALIDATE syntax before execution
  2. TEST with multiple context scenarios
  3. CHECK for null/undefined handling
  4. VERIFY type safety
  5. SCAN for security vulnerabilities

Quick Expression Checklist:

  • Valid JavaScript syntax
  • Context variables properly referenced ($json, $node)
  • Null-safe access patterns (?., ??)
  • No dangerous functions (eval, Function)
  • Efficient for large data sets

Common Pitfalls:

  • Accessing nested properties without null checks
  • Type coercion issues
  • Missing fallback values
  • Inefficient array operations

Quick Reference Card

n8n Expression Syntax

Pattern Example Description
Basic access {{ $json.field }} Access JSON field
Nested access {{ $json.user.email }} Access nested property
Array access {{ $json.items[0] }} Access array element
Node reference {{ $node["Name"].json.id }} Access other node's data
Method call {{ $json.name.toLowerCase() }} Call string method
Conditional {{ $json.x ? "yes" : "no" }} Ternary expression

Context Variables

Variable Description Example
$json Current item data {{ $json.email }}
$node["Name"] Other node's data {{ $node["HTTP"].json.body }}
$items() Multiple items {{ $items("Node", 0, 0).json }}
$now Current timestamp {{ $now.toISO() }}
$today Today's date {{ $today }}
$runIndex Run iteration {{ $runIndex }}
$workflow Workflow info {{ $workflow.name }}

Expression Syntax Patterns

Safe Data Access

// BAD: Can fail if nested objects are null
{
  {
    $json.user.profile.email;
  }
}

// GOOD: Optional chaining with fallback
{
  {
    $json.user?.profile?.email ?? '';
  }
}

// BAD: Array access without bounds check
{
  {
    $json.items[0].name;
  }
}

// GOOD: Safe array access
{
  {
    $json.items?.[0]?.name ?? 'No items';
  }
}

Type Conversions

// String to Number
{
  {
    parseInt($json.quantity, 10);
  }
}
{
  {
    parseFloat($json.price);
  }
}
{
  {
    Number($json.value);
  }
}

// Number to String
{
  {
    String($json.id);
  }
}
{
  {
    $json.amount.toString();
  }
}
{
  {
    $json.count.toFixed(2);
  }
}

// Date handling
{
  {
    new Date($json.timestamp).toISOString();
  }
}
{
  {
    DateTime.fromISO($json.date).toFormat('yyyy-MM-dd');
  }
}

// Boolean conversion
{
  {
    Boolean($json.active);
  }
}
{
  {
    $json.enabled === 'true';
  }
}

String Operations

// Case conversion
{
  {
    $json.name.toLowerCase();
  }
}
{
  {
    $json.name.toUpperCase();
  }
}
{
  {
    $json.name.charAt(0).toUpperCase() + $json.name.slice(1);
  }
}

// String manipulation
{
  {
    $json.text.trim();
  }
}
{
  {
    $json.text.replace(/\s+/g, ' ');
  }
}
{
  {
    $json.text.substring(0, 100);
  }
}

// Template strings
{
  {
    `Hello, ${$json.firstName} ${$json.lastName}!`;
  }
}
{
  {
    `Order #${$json.orderId} - ${$json.status}`;
  }
}

Array Operations

// Mapping
{
  {
    $json.items.map((item) => item.name);
  }
}
{
  {
    $json.items.map((item) => ({ id: item.id, total: item.price * item.qty }));
  }
}

// Filtering
{
  {
    $json.items.filter((item) => item.active);
  }
}
{
  {
    $json.items.filter((item) => item.price > 100);
  }
}

// Reducing
{
  {
    $json.items.reduce((sum, item) => sum + item.price, 0);
  }
}
{
  {
    $json.items.reduce((acc, item) => ({ ...acc, [item.id]: item }), {});
  }
}

// Finding
{
  {
    $json.items.find((item) => item.id === $json.targetId);
  }
}
{
  {
    $json.items.findIndex((item) => item.name === 'target');
  }
}

// Joining
{
  {
    $json.tags.join(', ');
  }
}
{
  {
    $json.items.map((i) => i.name).join(' | ');
  }
}

Validation Patterns

// Validate expression syntax
function validateExpressionSyntax(expression: string): ValidationResult {
  // Remove n8n template markers
  const code = expression.replace(/\{\{|\}\}/g, '').trim();

  try {
    // Check if valid JavaScript
    new Function(`return (${code})`);
    return { valid: true };
  } catch (error) {
    return {
      valid: false,
      error: error.message,
      suggestion: suggestFix(error.message, code),
    };
  }
}

// Validate context variables
function validateContextVariables(expression: string): string[] {
  const contextVars = ['$json', '$node', '$items', '$now', '$today', '$runIndex', '$workflow'];
  const usedVars = [];
  const invalidVars = [];

  // Find all $ prefixed variables
  const varPattern = /\$\w+/g;
  let match;

  while ((match = varPattern.exec(expression)) !== null) {
    const varName = match[0];
    if (contextVars.some((cv) => varName.startsWith(cv))) {
      usedVars.push(varName);
    } else {
      invalidVars.push(varName);
    }
  }

  return { usedVars, invalidVars };
}

// Test expression with sample data
function testExpression(expression: string, context: any): TestResult {
  const code = expression.replace(/\{\{|\}\}/g, '').trim();

  try {
    // Create function with context
    const fn = new Function('$json', '$node', '$items', '$now', '$today', `return (${code})`);

    const result = fn(
      context.$json || {},
      context.$node || {},
      context.$items || (() => ({})),
      context.$now || new Date(),
      context.$today || new Date()
    );

    return { success: true, result };
  } catch (error) {
    return { success: false, error: error.message };
  }
}

Common Errors and Fixes

Undefined Property Access

// ERROR: Cannot read property 'email' of undefined
{
  {
    $json.user.email;
  }
}

// FIX 1: Optional chaining
{
  {
    $json.user?.email;
  }
}

// FIX 2: With fallback
{
  {
    $json.user?.email ?? 'no-email@example.com';
  }
}

// FIX 3: Conditional
{
  {
    $json.user ? $json.user.email : '';
  }
}

Type Errors

// ERROR: toLowerCase is not a function (when null)
{
  {
    $json.name.toLowerCase();
  }
}

// FIX: Null check first
{
  {
    $json.name?.toLowerCase() ?? '';
  }
}

// ERROR: toFixed is not a function (string instead of number)
{
  {
    $json.price.toFixed(2);
  }
}

// FIX: Parse as number first
{
  {
    parseFloat($json.price).toFixed(2);
  }
}

// ERROR: map is not a function (not an array)
{
  {
    $json.items.map((i) => i.name);
  }
}

// FIX: Ensure array
{
  {
    (Array.isArray($json.items) ? $json.items : []).map((i) => i.name);
  }
}

Node Reference Errors

// ERROR: Node "Previous Node" not found
{
  {
    $node['Previous Node'].json.data;
  }
}

// FIX: Use exact node name (case-sensitive)
{
  {
    $node['Previous Node1'].json.data;
  }
}

// FIX: Add fallback for safety
{
  {
    $node['Previous Node']?.json?.data ?? {};
  }
}

Security Patterns

Dangerous Functions to Avoid

// DANGEROUS: Never use eval
{
  {
    eval($json.code);
  }
}

// DANGEROUS: Dynamic function creation
{
  {
    new Function($json.code)();
  }
}

// DANGEROUS: setTimeout with string
{
  {
    setTimeout($json.code, 1000);
  }
}

// SAFE: Use explicit operations instead
{
  {
    $json.value * 2;
  }
}
{
  {
    JSON.parse($json.jsonString);
  }
}

Input Validation

// Validate email format
{
  {
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($json.email) ? $json.email : '';
  }
}

// Sanitize for HTML (basic)
{
  {
    $json.text.replace(
      /[<>&"']/g,
      (c) =>
        ({
          '<': '&lt;',
          '>': '&gt;',
          '&': '&amp;',
          '"': '&quot;',
          "'": '&#39;',
        })[c]
    );
  }
}

// Limit string length
{
  {
    $json.input.substring(0, 1000);
  }
}

// Validate number range
{
  {
    Math.min(Math.max(parseInt($json.value), 0), 100);
  }
}

Performance Optimization

Efficient Array Operations

// SLOW: Multiple iterations
{
  {
    $json.items
      .filter((i) => i.active)
      .map((i) => i.name)
      .join(', ');
  }
}

// FASTER: Single reduce
{
  {
    $json.items.reduce((acc, i) => (i.active ? (acc ? `${acc}, ${i.name}` : i.name) : acc), '');
  }
}

// SLOW: Nested loops
{
  {
    $json.items.map((i) => $json.categories.find((c) => c.id === i.categoryId));
  }
}

// FASTER: Create lookup map first (in Code node)
const categoryMap = Object.fromEntries($json.categories.map((c) => [c.id, c]));
return $json.items.map((i) => categoryMap[i.categoryId]);

Avoid in Expressions

// AVOID: Complex logic in expressions
{
  {
    $json.items.reduce((acc, item) => {
      const category = $json.categories.find((c) => c.id === item.catId);
      if (category && category.active) {
        acc.push({ ...item, categoryName: category.name });
      }
      return acc;
    }, []);
  }
}

// BETTER: Move to Code node for complex transformations

Testing Patterns

// Expression test suite
const expressionTests = [
  {
    name: 'Basic property access',
    expression: '{{ $json.name }}',
    context: { $json: { name: 'John' } },
    expected: 'John',
  },
  {
    name: 'Nested with optional chaining',
    expression: '{{ $json.user?.email ?? "default" }}',
    context: { $json: { user: null } },
    expected: 'default',
  },
  {
    name: 'Array mapping',
    expression: '{{ $json.items.map(i => i.id).join(",") }}',
    context: { $json: { items: [{ id: 1 }, { id: 2 }] } },
    expected: '1,2',
  },
  {
    name: 'Conditional expression',
    expression: '{{ $json.score >= 70 ? "Pass" : "Fail" }}',
    context: { $json: { score: 85 } },
    expected: 'Pass',
  },
  {
    name: 'Node reference',
    expression: '{{ $node["Previous"].json.result }}',
    context: { $node: { Previous: { json: { result: 'success' } } } },
    expected: 'success',
  },
];

// Run tests
for (const test of expressionTests) {
  const result = testExpression(test.expression, test.context);
  console.log(`${test.name}: ${result.result === test.expected ? 'PASS' : 'FAIL'}`);
}

Agent Coordination

Memory Namespace

aqe/n8n/expressions/
├── validations/*    - Expression validation results
├── patterns/*       - Discovered expression patterns
├── errors/*         - Common error catalog
└── optimizations/*  - Performance suggestions

Fleet Coordination

// Coordinate expression validation with workflow testing
await Task(
  'Validate expressions',
  {
    workflowId: 'wf-123',
    validateAll: true,
    testWithSampleData: true,
  },
  'n8n-expression-validator'
);

Related Skills


Remember

n8n expressions are JavaScript-like with special context variables ($json, $node, etc.). Testing requires:

  • Syntax validation
  • Context variable verification
  • Null safety checks
  • Type compatibility
  • Security scanning

Key patterns: Use optional chaining (?.) and nullish coalescing (??) for safety. Move complex logic to Code nodes. Always test with edge cases (null, undefined, empty arrays).