Back to Skills

n8n-expression-testing

proffesor-for-testing
Updated Today
87 views
99
21
99
View on GitHub
Othern8nexpressionsjavascriptdata-transformationvalidation

About

This skill validates and tests n8n workflow expressions, checking for correct syntax, context-aware scenarios, and common pitfalls. It helps developers optimize performance and ensure safety by detecting issues like null references or security vulnerabilities. Use it when building or debugging n8n data transformations.

Documentation

n8n Expression Testing

<default_to_action> 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 </default_to_action>

Quick Reference Card

n8n Expression Syntax

PatternExampleDescription
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

VariableDescriptionExample
$jsonCurrent item data{{ $json.email }}
$node["Name"]Other node's data{{ $node["HTTP"].json.body }}
$items()Multiple items{{ $items("Node", 0, 0).json }}
$nowCurrent timestamp{{ $now.toISO() }}
$todayToday's date{{ $today }}
$runIndexRun iteration{{ $runIndex }}
$workflowWorkflow 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 ?? '[email protected]' }}

// 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).

Quick Install

/plugin add https://github.com/proffesor-for-testing/agentic-qe/tree/main/n8n-expression-testing

Copy and paste this command in Claude Code to install this skill

GitHub 仓库

proffesor-for-testing/agentic-qe
Path: .claude/skills/n8n-expression-testing
agenticqeagenticsfoundationagentsquality-engineering

Related Skills

production-readiness

Meta

This Claude Skill performs comprehensive pre-deployment validation to ensure code is production-ready. It runs a complete audit pipeline including security scans, performance benchmarks, and documentation checks. Use it as a final deployment gate to generate a deployment checklist and verify all production requirements are met.

View skill

n8n-integration-testing-patterns

Other

This skill provides testing patterns for n8n workflow integrations, covering API contracts, authentication, rate limiting, and error handling. Use it when developing or validating n8n nodes that connect to external services. It helps ensure integrations are robust and handle real-world scenarios properly.

View skill

type-safety-validation

Meta

This skill enables end-to-end type safety across your entire application stack using Zod, tRPC, Prisma, and TypeScript. It ensures type safety from database operations through API layers to UI components, catching errors at compile time rather than runtime. Use it when building full-stack applications that require robust validation and type-safe data flow.

View skill

n8n-security-testing

Other

This Claude Skill provides automated security testing for n8n workflows, detecting credential exposure, validating OAuth flows, and verifying data sanitization. Developers should use it when validating n8n workflow security to check for insecure configurations and sensitive data handling. It performs critical checks like scanning for credentials in workflow JSON and ensuring proper encryption.

View skill