| 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
- VALIDATE syntax before execution
- TEST with multiple context scenarios
- CHECK for null/undefined handling
- VERIFY type safety
- 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) =>
({
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": ''',
})[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
- n8n-workflow-testing-fundamentals - Workflow testing
- n8n-security-testing - Security validation
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).