Tips > Building Workflows

Transform Data Shapes Between Incompatible Nodes

Different SaaS platforms structure data in incompatible ways.

When two SaaS platforms structure data differently, place a Code node between them as an adapter. It reshapes the upstream node's output into the exact structure the downstream node expects, mapping IDs, renaming fields, and translating values such as status labels. Centralizing this mapping in one Code node keeps future field changes easy to make.

Why use a Code node as an adapter between nodes?

Different SaaS platforms structure data in incompatible ways. A Code node between them acts as an adapter layer, reshaping the output of one node to match the expected input of another.

Real-world example: Syncing records from Airtable (which returns linked records as arrays of IDs) to Notion (which expects relation properties as arrays of page IDs with specific formatting).

How do you reshape data between incompatible nodes?

// Mode: Run Once for All Items

// Mapping of Airtable record IDs to Notion page IDs (pre-built or fetched)
const idMap = $input.first().json.idMapping; // { airtableId: notionPageId }

return $input.all().slice(1).map(item => {
  const record = item.json;

  // Airtable returns: { "Assignees": ["recABC", "recDEF"], "Status": "In Progress" }
  // Notion expects: specific property format per type

  return {
    json: {
      parent: { database_id: 'your-notion-database-id' },
      properties: {
        // Title property
        'Task Name': {
          title: [{ text: { content: record['Task Name'] || 'Untitled' } }]
        },
        // Select property
        'Status': {
          select: { name: mapStatus(record['Status']) }
        },
        // Relation property -- map Airtable IDs to Notion page IDs
        'Assignees': {
          relation: (record['Assignees'] || [])
            .map(airtableId => idMap[airtableId])
            .filter(Boolean)
            .map(notionId => ({ id: notionId }))
        },
        // Date property
        'Due Date': {
          date: record['Due Date']
            ? { start: record['Due Date'], end: null }
            : null
        },
        // Number property
        'Story Points': {
          number: parseInt(record['Story Points'], 10) || null
        },
      }
    }
  };
});

function mapStatus(airtableStatus) {
  const statusMap = {
    'To Do': 'Not started',
    'In Progress': 'In progress',
    'Done': 'Complete',
    'Blocked': 'Blocked',
  };
  return statusMap[airtableStatus] || 'Not started';
}

This pattern is reusable for any SaaS-to-SaaS sync. Keep the mapping logic centralized in one Code node to make future field changes easy.

Related: Use Edit Fields in "Map Each" Mode for Simple Renames · Configure Payload Size and Binary Data Mode for Large Files

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call