n8n Masterclass IModule 2

2.2Data Mapping and JS Expressions

25 min 10 code blocks Practice Lab Quiz (5Q)

Data Mapping and JS Expressions: The Logic Layer

The difference between a beginner n8n user and a production-grade automation engineer is one thing: JavaScript expressions. Every webhook delivers messy JSON. Every API returns data in its own format. Every client's Google Sheet has different column names. Your job is to transform chaos into clean, standardized data that flows perfectly between nodes. This lesson teaches you the expression language that makes n8n workflows intelligent — the same expressions that a Lahore freelancer uses to process 2,000 leads per month without a single manual data cleanup.

Section 1: The Expression Syntax

n8n uses a template-string-like syntax to access data from previous nodes. Understanding this is non-negotiable — every workflow you build will use expressions.

code
n8n EXPRESSION SYNTAX — THE COMPLETE REFERENCE
===============================================================

  BASIC ACCESS:
  ├── Current node data: {{ $json.field_name }}
  ├── Specific node: {{ $node["NodeName"].json.field_name }}
  ├── Parameters: {{ $node["NodeName"].parameter.param_name }}
  └── Environment: {{ $env.MY_VARIABLE }}

  DATA NAVIGATION:
  ├── Nested object: {{ $json.address.city }}
  ├── Array element: {{ $json.items[0].name }}
  ├── Array length: {{ $json.items.length }}
  └── Optional chain: {{ $json.address?.city ?? "Unknown" }}

  WORKFLOW CONTEXT:
  ├── Execution ID: {{ $execution.id }}
  ├── Workflow name: {{ $workflow.name }}
  ├── Current date: {{ new Date().toISOString() }}
  ├── PKT timestamp: {{ new Date().toLocaleString('en-PK',
  │   {timeZone: 'Asia/Karachi'}) }}
  └── Item index: {{ $itemIndex }}

  WHERE EXPRESSIONS WORK:
  ├── Set node → field values
  ├── IF node → conditions
  ├── HTTP Request → URL, headers, body
  ├── Gmail → subject, body, to address
  ├── Google Sheets → column values
  └── Basically everywhere you see the {{ }} icon

===============================================================

The Set Node vs. Code Node Decision

TaskUse Set NodeUse Code Node
Rename a field{{ $json.old_name }}Overkill
Simple math{{ $json.price * 1.17 }}Overkill
String concatenation{{ $json.first + ' ' + $json.last }}Overkill
Loop through arrayCannotfor (const item of $input.all()) {...}
Complex conditional logicLimitedFull JS if/else/switch
Call external functionCannotconst result = someFunction()
Process 100+ items differentlyAwkward$input.all().map(item => ...)

Rule of thumb: If it's a one-liner, use the Set node. If it needs a loop, conditional branches, or multi-step logic, use the Code node.

Section 2: Essential Data Transformations

These are the transformations you'll use in 80% of Pakistani automation workflows:

Phone Number Normalization (Critical for WATI/WhatsApp)

Pakistani phone numbers arrive in chaos: 03001234567, +923001234567, 0300-123-4567, 92300 1234567, 300 1234567. Your WhatsApp API needs exactly one format: +923001234567.

javascript
// UNIVERSAL PAKISTANI PHONE NORMALIZER
// Use in a Code node for reliability

for (const item of $input.all()) {
  let phone = String(item.json.phone || '');

  // Step 1: Remove all non-digit characters
  phone = phone.replace(/[^0-9]/g, '');

  // Step 2: Handle different starting patterns
  if (phone.startsWith('92') && phone.length === 12) {
    phone = '+' + phone;                    // 923001234567 -> +923001234567
  } else if (phone.startsWith('0') && phone.length === 11) {
    phone = '+92' + phone.substring(1);     // 03001234567 -> +923001234567
  } else if (phone.length === 10 && phone.startsWith('3')) {
    phone = '+92' + phone;                  // 3001234567 -> +923001234567
  } else {
    phone = 'INVALID: ' + item.json.phone;  // Flag for manual review
  }

  item.json.phone_normalized = phone;
  item.json.phone_valid = !phone.startsWith('INVALID');
}
return $input.all();
code
PHONE FORMAT CONVERSION TABLE
===============================================================

  INPUT FORMAT          ->  OUTPUT (E.164)     STATUS
  ────────────────────────────────────────────────────
  03001234567           ->  +923001234567      Valid
  +923001234567         ->  +923001234567      Valid
  0300-123-4567         ->  +923001234567      Valid
  92300 1234567         ->  +923001234567      Valid
  300 1234567           ->  +923001234567      Valid
  923001234567          ->  +923001234567      Valid
  1234567               ->  INVALID            Flagged
  +1-555-123-4567       ->  INVALID            Flagged

  WHY THIS MATTERS:
  ├── WATI API requires +92XXXXXXXXXX format exactly
  ├── One wrong digit = failed WhatsApp message
  ├── Failed message = missed customer notification
  ├── Missed notification = unhappy client = lost retainer
  └── This one Code node prevents all of that

===============================================================

Name Cleaning and Title Case

javascript
// Clean and format names from messy form submissions
for (const item of $input.all()) {
  let name = String(item.json.name || '');

  // Trim whitespace, collapse multiple spaces
  name = name.trim().replace(/\s+/g, ' ');

  // Convert to Title Case
  name = name.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());

  item.json.name_clean = name;

  // Split into first/last for email personalization
  const parts = name.split(' ');
  item.json.first_name = parts[0] || '';
  item.json.last_name = parts.slice(1).join(' ') || '';
}
return $input.all();

Lead Score Conditional Routing

javascript
// Expression in Set node (no Code node needed):
// Priority label based on score
{{ $json.score >= 80 ? "HOT"
   : $json.score >= 60 ? "WARM"
   : "COLD" }}

// Urgency flag for WhatsApp alert
{{ $json.score >= 80 ? "CALL IMMEDIATELY"
   : $json.score >= 60 ? "Follow up within 24 hours"
   : "Add to nurture list" }}

// PKR formatting
{{ "PKR " + Number($json.amount).toLocaleString('en-PK') }}
// Input: 45000 -> Output: "PKR 45,000"

Domain Extraction from Email

javascript
// Extract company domain for enrichment
{{ $json.email.split('@')[1] }}
// ahmed@techstartup.pk -> techstartup.pk

// Extract domain from URL (Code node for reliability)
for (const item of $input.all()) {
  let url = String(item.json.website || '');
  url = url.replace(/^https?:\/\//, '')  // Remove protocol
           .replace(/^www\./, '')         // Remove www
           .split('/')[0]                 // Remove path
           .split(':')[0]                 // Remove port
           .toLowerCase();               // Normalize case
  item.json.domain = url;
}
return $input.all();

Section 3: Binary vs. JSON Data

code
n8n DATA TYPES — WHEN EACH APPLIES
===============================================================

  JSON DATA (Text, Numbers, Objects):
  ├── Webhook payloads
  ├── API responses
  ├── Google Sheets data
  ├── AI-generated text
  ├── Form submissions
  └── Access via: {{ $json.field_name }}

  BINARY DATA (Files, Images, PDFs):
  ├── Email attachments
  ├── Downloaded files (HTTP Request)
  ├── Generated PDFs/images
  ├── Uploaded files
  └── Access via: {{ $binary.data }} or {{ $binary.attachment }}

  COMMON MISTAKE:
  ├── You generate a PDF report via an AI/Code node
  ├── You try to attach it to Gmail using {{ $json.report }}
  ├── Gmail sends an empty email — no attachment
  ├── WHY: The PDF is binary data, not JSON
  ├── FIX: Use {{ $binary.data }} in the Gmail attachment field
  └── Or use the "Move Binary Data" node to convert

  CONVERSION NODES:
  ├── "Move Binary Data" — converts between JSON and binary
  ├── "Read Binary File" — reads file from disk as binary
  ├── "Write Binary File" — saves binary to disk
  └── "Spreadsheet File" — converts CSV/Excel to JSON rows

===============================================================

Section 4: Advanced Expression Patterns

Date and Time Handling for Pakistan (PKT = UTC+5)

javascript
// Current PKT timestamp (expression)
{{ new Date().toLocaleString('en-PK', {timeZone: 'Asia/Karachi'}) }}

// Is it business hours in Pakistan? (Code node)
for (const item of $input.all()) {
  const pktHour = new Date().toLocaleString('en-US', {
    timeZone: 'Asia/Karachi',
    hour: 'numeric',
    hour12: false
  });
  const hour = parseInt(pktHour);
  item.json.is_business_hours = (hour >= 9 && hour < 18);
  item.json.pkt_hour = hour;

  // Route: business hours -> immediate action, after hours -> queue
  item.json.action = item.json.is_business_hours
    ? "Send WhatsApp now"
    : "Queue for 9 AM PKT tomorrow";
}
return $input.all();

PKR Currency Formatting

javascript
// Simple expression for amounts
{{ "PKR " + Number($json.amount).toLocaleString('en-PK') }}
// 1500000 -> "PKR 1,500,000"

// Code node with GST calculation
for (const item of $input.all()) {
  const amount = Number(item.json.amount);
  const gst = amount * 0.17;  // Pakistan GST rate: 17%
  item.json.subtotal = `PKR ${amount.toLocaleString('en-PK')}`;
  item.json.gst = `PKR ${gst.toLocaleString('en-PK')}`;
  item.json.total = `PKR ${(amount + gst).toLocaleString('en-PK')}`;
  item.json.total_raw = amount + gst;
}
return $input.all();

Array Processing (Multiple Items)

javascript
// Process all line items from a Shopify order
for (const item of $input.all()) {
  const lineItems = item.json.line_items || [];

  item.json.product_summary = lineItems
    .map(li => `${li.name} x${li.quantity}`)
    .join(', ');

  item.json.total_quantity = lineItems
    .reduce((sum, li) => sum + li.quantity, 0);

  item.json.item_count = lineItems.length;
}
return $input.all();
// Input: [{name:"Blue Kurta", quantity:2}, {name:"White Shalwar", quantity:1}]
// Output: product_summary = "Blue Kurta x2, White Shalwar x1"
//         total_quantity = 3
//         item_count = 2
Practice Lab

Practice Lab

Exercise 1: Phone Number Normalizer — Create a Manual Trigger with 10 Pakistani phone numbers in different formats (03001234567, +92-321-456-7890, 0300 123 4567, 3451234567, 92-300-123-4567, and 5 more variations). Add a Code node with the Universal Pakistani Phone Normalizer from Section 2. Run the workflow. Verify all valid numbers output as +92XXXXXXXXXX and invalid numbers are flagged. Save this Code node as a reusable snippet — you'll use it in every Pakistani automation.

Exercise 2: Name Cleaner + Title Case — Create a Manual Trigger with messy names: " taqi NAQVI ", "AHMED khan", " maria ALI butt ", "zara". Add a Code node that trims, normalizes to Title Case, and splits into first_name/last_name. Verify outputs: "Taqi Naqvi" (first: "Taqi", last: "Naqvi"), "Ahmed Khan", "Maria Ali Butt" (first: "Maria", last: "Ali Butt"), "Zara" (first: "Zara", last: "").

Exercise 3: Revenue Calculator — Create a workflow that takes a deal size in PKR and a closing probability (0-1). Use expressions to calculate: Expected Value = amount * probability, formatted as "PKR 500,000". Add GST calculation (17%). Add USD conversion at a fixed rate (1 USD = PKR 280). Output should show: subtotal, GST, total PKR, and total USD. Test with PKR 100,000 at 0.7 probability.

Exercise 4: Full Data Pipeline — Build an end-to-end transformation: Webhook receives raw lead data (messy name, unformatted phone, email, deal amount as string). Set node / Code node cleans everything: Title Case name, E.164 phone, domain extracted from email, PKR formatted amount, lead score conditional label (HOT/WARM/COLD), PKT timestamp. Output the clean JSON and append to a Google Sheet. This is the "data cleaning layer" that sits between every trigger and every action node in production workflows.

Pakistan Case Study

Lahore Lead Processing Agency — 2,000 Leads/Month on Autopilot

Usman ran a lead generation service from Johar Town, Lahore. His clients (3 real estate agencies) would upload CSV files of property inquiries to a shared Google Sheet. Usman's job: clean the data, normalize phone numbers, score the leads, and distribute to the right sales agent. He was doing this manually — 2 hours per day, 5 days a week.

Before n8n:

TaskTime/DayMonthly Cost (at PKR 400/hour)
Phone number cleanup30 minPKR 6,000
Name formatting15 minPKR 3,000
Lead scoring (manual review)45 minPKR 9,000
Agent assignment15 minPKR 3,000
WhatsApp notifications15 minPKR 3,000
Total2 hours/dayPKR 24,000/month

His n8n Pipeline (3 hours to build):

NodePurposeExpression Used
Google Sheets TriggerWatch for new rowsPoll every 2 min
Code Node 1Phone normalizerUniversal PK normalizer
Code Node 2Name cleaner + Title Casetrim + replace + Title Case
Set NodeDomain extraction{{ $json.email.split('@')[1] }}
Set NodePKR formatting{{ "PKR " + Number($json.amount).toLocaleString() }}
IF NodeLead scoring threshold{{ $json.score >= 70 }}
Code Node 3Agent assignment logicScore + city routing
WATI HTTPWhatsApp to assigned agentPersonalized alert
Google SheetsWrite clean data to CRM tabAll transformed fields

Results After 30 Days:

MetricBefore (Manual)After (n8n Expressions)Change
Leads processed/month2,000 (barely)2,000 (effortless)Same volume, zero effort
Processing time2 hours/day0 (fully automated)-100%
Phone format errors50-80/month0-100%
Name formatting errors30-40/month0-100%
Monthly labor costPKR 24,000PKR 0-100%
Capacity for new clients0 (maxed out)3-4 more at same volumeUnlimited

Usman's Insight: "Pehle main 2 ghante roz phone numbers theek karta tha — har format alag, WATI reject karta tha. Ab ek Code node hai jo har format ko +92 mein convert kar deta hai. 30 din mein ek bhi failed WhatsApp message nahi aayi. Ab 3 clients ki jagah 6 clients handle kar sakta hun — same effort, double revenue."

Key Takeaways

  • JavaScript expressions are the logic layer that makes n8n workflows intelligent — every Set node, IF node, and HTTP Request uses them
  • Use the Set node for simple one-liners (field rename, basic math, string concatenation) and the Code node for loops, complex conditionals, and multi-step transformations
  • The Universal Pakistani Phone Normalizer (Section 2) is the single most reusable Code node for any Pakistani automation — save it and deploy in every project
  • Always normalize data BEFORE it reaches your CRM, WhatsApp API, or email node — one malformatted phone number means one failed customer notification
  • Binary data (files, images, PDFs) and JSON data (text, numbers) are different types in n8n — use the "Move Binary Data" node to convert between them
  • PKT timezone handling ({timeZone: 'Asia/Karachi'}) is essential for scheduling and timestamp accuracy in Pakistani workflows
  • The toLocaleString('en-PK') method formats PKR amounts correctly with commas — use it everywhere you display money to clients
  • Array processing in Code nodes ($input.all().map(), .reduce(), .filter()) handles multi-item data like Shopify line items and bulk CSV imports
  • A data cleaning layer (phone + name + domain + amount formatting) should sit between every trigger and every action node — it takes 30 minutes to build and prevents 100% of data format errors

Lesson Summary

Includes hands-on practice lab10 runnable code examples5-question knowledge check below

Quiz: Data Mapping and JS Expressions

5 questions to test your understanding. Score 60% or higher to pass.