n8n Binary Data: The Complete Guide to Working with Files in Workflows
n8n Binary Data: The Complete Guide to Working with Files in Workflows
Logic Workflow Team

n8n Binary Data: The Complete Guide to Working with Files in Workflows

#n8n #binary data #files #tutorial #workflow automation

Every file in n8n is invisible until you understand binary data.

You download a PDF from an API. The node shows success. But when you try to access the content in the next node, nothing’s there. Or worse, your entire workflow crashes with a cryptic “binary file not found” error.

This happens constantly in the n8n community. Users build file-processing workflows, test them with small files, and watch helplessly as production data triggers memory errors or silently loses attachments.

The Hidden Data Layer

When you receive an email attachment, download an image, or accept a file upload, that file doesn’t live in $json like your other data. It exists in a separate layer called binary data. Most nodes can’t see it. Most expressions can’t access it. And if you don’t handle it correctly, it vanishes without warning.

Understanding binary data is the difference between workflows that work in testing and workflows that survive production. For the official reference, see the n8n binary data documentation.

What You’ll Learn

  • How binary data differs from JSON and why n8n stores them separately
  • The binary property structure and how to identify property names
  • Three methods for accessing binary data (including the new Edit Fields feature)
  • Why files “disappear” between nodes and how to prevent it
  • Memory optimization techniques for large file workflows
  • Production-ready patterns for email attachments, form uploads, and API downloads
  • Debugging techniques when binary data behaves unexpectedly

What Binary Data Actually Is

In n8n, data flows through your workflow in two distinct formats. Understanding this split prevents 90% of file-handling errors.

JSON Data vs Binary Data

JSON data is what you interact with most often. It’s the structured key-value pairs you see in the output panel, access with expressions like {{ $json.name }}, and manipulate with Edit Fields. JSON handles text, numbers, booleans, arrays, and objects.

Binary data is everything else. Images, PDFs, spreadsheets, audio files, videos, ZIP archives. Any file content that isn’t plain text. This data is stored separately from JSON because file contents would be impractical to display and manipulate as regular text.

When you download a file via the HTTP Request node or receive an email attachment, the file content doesn’t appear in $json. Instead, it lives in a binary property attached to the same item.

Why n8n Separates Them

Consider a 5MB PDF. If n8n stored that file directly in JSON:

  • The execution log would be unreadable
  • Memory usage would spike unpredictably
  • Copying data between nodes would duplicate massive file contents
  • The UI would struggle to render outputs

By storing binary data separately, n8n keeps JSON lightweight while maintaining full file access when you need it.

The Base64 Reality

Under the hood, binary data is stored as Base64-encoded strings. Base64 converts binary content (raw bytes) into text characters that can be safely stored and transmitted. This encoding increases file size by about 33%, but ensures compatibility across systems.

You rarely interact with Base64 directly. The nodes handle encoding and decoding automatically. But knowing it exists helps when debugging or when APIs require Base64 input.

The Binary Property Structure

Every n8n item can carry both JSON and binary data. Understanding the structure helps you access files reliably.

Anatomy of an Item with Binary Data

When a node outputs binary data, the item structure looks like this:

{
  "json": {
    // Regular JSON properties, accessible via $json expressions
    "fileName": "report.pdf",
    "fileSize": 102400,
    "uploadedBy": "user@example.com"
  },
  "binary": {
    // Binary data lives here, separate from JSON
    "data": {
      "mimeType": "application/pdf",
      "fileName": "report.pdf",
      "fileExtension": "pdf",
      "data": "JVBERi0xLjQK..."  // Base64-encoded file content
    }
  }
}

The binary object can contain multiple properties, each representing a different file. The property name (data in this example) is how you reference that specific file.

Binary Property Naming Conventions

Different nodes use different property names:

SourceDefault Binary Property Name
HTTP Requestdata
Read/Write Files from Diskdata
Gmail (attachments)attachment_0, attachment_1, etc.
Google Drive (download)data
Form Trigger (file upload)Field name from your form
Webhook (file upload)data or custom name

This inconsistency is the #1 cause of “binary file not found” errors. When a node expects data but your file lives in attachment_0, the operation fails.

Identifying Binary Property Names

To find the correct property name:

  1. Click on the node that outputs binary data
  2. Look at the output panel
  3. Switch to the Binary tab
  4. The property name appears above the file preview

Always verify this before configuring downstream nodes that consume binary data.

Multiple Files Per Item

A single item can carry multiple binary files:

{
  "json": { "emailSubject": "Documents attached" },
  "binary": {
    "attachment_0": { "fileName": "invoice.pdf", ... },
    "attachment_1": { "fileName": "receipt.pdf", ... },
    "attachment_2": { "fileName": "contract.docx", ... }
  }
}

Each file has its own property name. Access them independently using their specific names.

Where Binary Data Comes From

Binary data enters your workflow through specific nodes designed to handle files. Here are the most common sources.

HTTP Request Node

When downloading files from URLs, the HTTP Request node automatically outputs binary data:

HTTP Request (GET image URL) → Binary data in "data" property

Configure HTTP Request for file downloads:

  • Method: GET
  • URL: The file URL
  • The node detects binary content and stores it appropriately

Email Triggers (Gmail, Outlook, IMAP)

Email attachments arrive as binary data with numbered property names:

Gmail Trigger → Attachments in "attachment_0", "attachment_1", etc.

Each attachment becomes a separate binary property. The JSON includes metadata about the attachment (filename, size), but the actual file content is in binary.

Cloud Storage Downloads

Google Drive, Dropbox, S3, and other storage nodes output downloaded files as binary:

Google Drive (Download) → Binary data in "data" property

Most cloud storage nodes use data as the default property name.

Form Trigger File Uploads

When users upload files through n8n forms:

Form Trigger → Binary data in property named after your form field

If your form has a file field named resume, the binary property will be resume. This differs from other nodes that use data.

Read/Write Files from Disk

For self-hosted n8n reading local files:

Read/Write Files from Disk → Binary data in "data" property

This node can read single files or multiple files using glob patterns.

Webhook File Uploads

External systems sending files to your webhook:

Webhook → Binary data (property name depends on sender)

Check your webhook’s output panel to identify the property name. Enable “Raw Body” in webhook options if you’re receiving files.

Source Reference Table

NodeBinary PropertyNotes
HTTP RequestdataAutomatic for binary responses
Gmail Triggerattachment_0, attachment_1Numbered sequentially
Microsoft Outlook Triggerattachment_0, attachment_1Same pattern as Gmail
Google DrivedataDownload operation only
DropboxdataDownload operation only
Read/Write FilesdataSingle or multiple files
Form TriggerForm field nameMatches your field configuration
WebhookVariesCheck output panel
FTPdataDownload operation

Accessing Binary Data: Three Methods

Once binary data exists in your workflow, you have three ways to work with it.

The Edit Fields node now supports direct binary data manipulation. This is the cleanest approach for most scenarios.

Version note: Binary data support in Edit Fields was added in n8n version 1.0. If you’re on an older version, use the Code node method instead.

Accessing binary metadata:

In Edit Fields, create a new field and use expressions:

// Get the filename
{{ $binary.data.fileName }}

// Get the MIME type
{{ $binary.data.mimeType }}

// Get the file extension
{{ $binary.data.fileExtension }}

Renaming binary properties:

If your source uses attachment_0 but your destination expects data, use Edit Fields:

  1. Add an Edit Fields node
  2. Enable “Include Other Input Fields” (this preserves JSON)
  3. In the Binary section, add a mapping: data = {{ $binary.attachment_0 }}

Copying binary between items:

Use expressions to reference binary from other items in the same execution.

Method 2: Expressions in Any Node

Many nodes accept expressions for binary property configuration. Use these expressions wherever you can:

// Reference binary from the current item
{{ $binary.data }}

// Access specific metadata
{{ $binary.data.fileName }}
{{ $binary.data.mimeType }}
{{ $binary.data.fileSize }}

// Reference binary from a specific node
{{ $('HTTP Request').item.binary.data }}

// Check if binary exists
{{ $binary.data ? 'has file' : 'no file' }}

These expressions work in most node configuration fields that accept expressions.

Method 3: Code Node (For Advanced Manipulation)

When you need to manipulate actual file content, use the Code node with getBinaryDataBuffer():

// Get the binary data buffer for the first item
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');

// Now you can work with the raw bytes
// Example: Calculate file hash
const crypto = require('crypto');
const hash = crypto.createHash('md5').update(binaryDataBuffer).digest('hex');

// Return the hash while preserving the original binary
return [{
  json: {
    fileHash: hash,
    fileName: $input.first().binary.data.fileName
  },
  binary: $input.first().binary
}];

When to use Code node:

  • Calculating checksums or hashes
  • Modifying file content (adding headers to CSV, etc.)
  • Combining multiple files
  • Complex validation logic
  • Interfacing with libraries that need raw bytes

Important: Always use getBinaryDataBuffer() instead of directly accessing the data property. The helper function handles n8n’s internal storage format correctly. See the official getBinaryDataBuffer documentation for more details.

For complete Code node reference, see our Code node guide.

The “Disappearing Binary Data” Problem

This is the most frustrating issue users encounter. You have binary data, you add a node in the middle, and suddenly the file is gone.

Why Binary Data Vanishes

Cause 1: Edit Fields in “Set” Mode

The Edit Fields node has two modes:

  • Manual Mapping (default): Replaces everything, including binary
  • Keep All Fields: Preserves existing data, adds your changes

If you use Manual Mapping and don’t explicitly include binary data, it gets discarded.

Cause 2: Nodes That Reset Item Structure

Some nodes create entirely new items rather than passing through existing ones:

  • Aggregate/Summarize nodes combine items
  • Split operations create new items from arrays
  • Some transform nodes rebuild items from scratch

When items are recreated, binary data doesn’t automatically transfer.

Cause 3: Branch Merging Without Binary Consideration

When merging workflow branches, the Merge node behavior depends on mode:

  • Append: Keeps all items and their binary data
  • Combine: May lose binary depending on configuration
  • Choose Branch: Only passes items from selected branch

Solution 1: Use “Keep All Fields” Mode

In Edit Fields, switch to “Keep All Fields” to preserve binary:

  1. Click the mode selector (top of Edit Fields panel)
  2. Select “Keep All Fields”
  3. Add only the fields you want to change
  4. Existing JSON and binary pass through unchanged

Solution 2: Explicitly Preserve Binary in Code

When using Code node, always return binary data:

// WRONG: Binary data is lost
return items.map(item => ({
  json: {
    ...item.json,
    newField: 'value'
  }
  // Binary not included = lost
}));

// CORRECT: Binary data preserved
return items.map(item => ({
  json: {
    ...item.json,
    newField: 'value'
  },
  binary: item.binary  // Explicitly preserve binary
}));

Solution 3: Restructure Your Workflow

If intermediate nodes must modify item structure, extract from binary first:

Source → Extract from File → [Your transforms] → Convert to File (if needed)

Instead of:

Source → [Your transforms that lose binary] → Extract from File (fails)

Extract early, transform freely, convert back if needed.

Solution 4: Check Binary at Each Step

During development, verify binary exists after each node:

  1. Run your workflow step by step
  2. After each node, check the output panel’s Binary tab
  3. If binary disappears, that node is the culprit

This systematic approach quickly identifies where data loss occurs.

Converting Between Binary and JSON

Sometimes you need data out of files (binary to JSON) or data into files (JSON to binary).

Binary to JSON: Extract from File

The Extract from File node reads binary data and outputs structured JSON:

Binary PDF → Extract from File → JSON with text content
Binary CSV → Extract from File → JSON rows
Binary Excel → Extract from File → JSON rows with columns

Configuration:

  • Operation: Match your file type (PDF, CSV, XLSX, etc.)
  • Binary Property: Name of the property containing your file (usually data)

Supported formats: CSV, XLSX, XLS, PDF, HTML, JSON, ICS, ODS, RTF, Text

JSON to Binary: Convert to File

The Convert to File node transforms JSON data into downloadable files:

JSON data → Convert to File → Binary CSV/Excel/JSON file

Configuration:

  • Operation: Target format (CSV, XLSX, JSON, etc.)
  • File Name: Output filename with extension

Use cases:

  • Creating reports for email attachment
  • Generating exports for cloud storage upload
  • Building configuration files

XML File Handling

XML files require a two-step process. The XML node works with string data, not binary:

Binary XML file → Extract from File (as Text) → XML node → JSON

The Extract from File node converts binary to text, then the XML node handles the XML-to-JSON conversion. See our XML node guide for detailed patterns.

Conversion Reference Table

Source FormatTarget FormatNode Sequence
PDF fileJSON textExtract from File (PDF operation)
Excel fileJSON rowsExtract from File (XLSX operation)
CSV fileJSON rowsExtract from File (CSV operation)
JSON dataCSV fileConvert to File (CSV operation)
JSON dataExcel fileConvert to File (XLSX operation)
JSON dataJSON fileConvert to File (JSON operation)
XML fileJSON dataExtract from File (Text) → XML node
JSON dataXML fileXML node (JSON to XML) → Convert to File (Text)

Memory Management for Large Files

Binary data handling can crash your n8n instance. Here’s how to prevent it. For official guidance, see the n8n scaling binary data documentation.

The Memory Problem

By default, n8n stores binary data in memory. This works fine for small files. But when you:

  • Process files larger than 10-20MB
  • Handle many files simultaneously
  • Run multiple workflows with binary data

Memory consumption spikes. Eventually you see:

FATAL ERROR: Ineffective mark-compacts near heap limit
Allocation failed - JavaScript heap out of memory

Practical File Size Limits

There’s no official hard limit, but here’s what works reliably in practice:

Storage ModeReliable File SizeNotes
Default (memory)Up to 10-15MBSafe for typical documents
Filesystem modeUp to 100MB+Limited by disk space and timeout
S3 mode500MB+Limited by S3 and workflow timeout

Real-world guidance:

  • Under 10MB: Any mode works. Most PDFs, images, and spreadsheets fit here.
  • 10-50MB: Use filesystem mode. Typical for large Excel files, high-res images.
  • 50-200MB: Use filesystem mode with increased timeout. Videos, large datasets.
  • Over 200MB: Use S3 mode. Consider chunked processing or streaming via external tools.

These limits also depend on how many files you process concurrently. Processing 100 small files simultaneously can exceed memory faster than one large file.

Solution 1: Filesystem Mode

Tell n8n to store binary data on disk instead of in memory:

# In your environment variables or .env file
N8N_DEFAULT_BINARY_DATA_MODE=filesystem

With filesystem mode:

  • Binary data writes to temporary files
  • Memory usage stays constant regardless of file size
  • Slight I/O overhead, but prevents crashes

This is the minimum configuration for production workflows handling files.

Solution 2: Increase Node.js Memory

If filesystem mode isn’t enough, increase the available heap:

# Allow Node.js to use more memory (2GB example)
NODE_OPTIONS=--max-old-space-size=2048

Combine with filesystem mode for best results. But don’t rely on this alone; it just delays the crash with very large files.

Solution 3: S3 External Storage

For production deployments, especially with queue mode, use S3-compatible storage:

N8N_AVAILABLE_BINARY_DATA_MODES=filesystem,s3
N8N_DEFAULT_BINARY_DATA_MODE=s3

# S3 Configuration
N8N_EXTERNAL_STORAGE_S3_HOST=s3.amazonaws.com
N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME=your-bucket
N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION=us-east-1
N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY=your-access-key
N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET=your-secret-key

S3 storage enables:

  • Unlimited file sizes
  • Shared access across queue mode workers
  • Persistent binary data between restarts
  • Better scalability for high-volume workflows

Solution 4: Process in Batches

For workflows processing many files, use the Split In Batches node:

Get 1000 files → Split In Batches (50 at a time) → Process → Loop back

This limits concurrent memory usage even with many files.

See our batch processing guide for detailed patterns.

Environment Variable Reference

VariableDefaultPurpose
N8N_DEFAULT_BINARY_DATA_MODEdefault (memory)Primary storage mode
N8N_AVAILABLE_BINARY_DATA_MODESdefaultAvailable storage options
NODE_OPTIONS-Node.js configuration including heap size
N8N_EXTERNAL_STORAGE_S3_*-S3 configuration (multiple variables)

For complete configuration options, see the official binary data environment variables documentation.

Binary Data in Distributed Environments

If you’re running n8n with queue mode and multiple workers, binary data requires special consideration.

The Worker Problem

In queue mode:

  • The main instance receives triggers and webhooks
  • Worker instances execute workflows
  • Multiple workers might run on different servers

With default memory storage, binary data exists only in the process that received it. When a worker picks up the job, it can’t access binary data from the main instance.

Filesystem Mode Isn’t Enough

Filesystem mode writes to local disk. But if your main instance and workers run on different machines, they don’t share the filesystem. The worker can’t read files written by the main instance.

S3 Solves Distribution

S3 storage works across distributed environments:

  1. Main instance receives file upload
  2. Binary data writes to S3
  3. Worker picks up job
  4. Worker reads binary data from S3 (same bucket, same data)

All instances access the same storage, regardless of which machine runs which process.

Configuration for Queue Mode

Ensure all n8n processes (main and workers) have identical S3 configuration:

# On ALL instances (main and workers)
EXECUTIONS_MODE=queue
N8N_DEFAULT_BINARY_DATA_MODE=s3

# Same S3 credentials everywhere
N8N_EXTERNAL_STORAGE_S3_HOST=s3.amazonaws.com
N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME=n8n-binary-data
N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION=us-east-1
N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY=your-access-key
N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET=your-secret-key

For complete queue mode setup including binary data handling, see our queue mode guide.

Debugging Binary Data Issues

When binary data misbehaves, systematic debugging finds the problem quickly.

Common Errors and Causes

Error MessageLikely CauseSolution
”Binary file ‘data’ not found”Wrong property nameCheck actual property name in output panel
”No binary data received”Upstream node didn’t output binaryVerify source node configuration
”The item has no binary field”Binary lost in intermediate nodeCheck node between source and error
”JavaScript heap out of memory”File too large for memory modeEnable filesystem mode
Empty extraction resultsWrong file format or corrupted fileVerify file type matches operation

Debugging Technique 1: Binary Tab Inspection

After every node that should have binary data:

  1. Run the workflow up to that point
  2. Click the node
  3. Look for the Binary tab in the output panel
  4. If missing, binary was lost or never created

Debugging Technique 2: Code Node Inspection

Add a Code node to inspect binary metadata:

const items = $input.all();

return items.map((item, index) => ({
  json: {
    itemIndex: index,
    hasBinary: !!item.binary,
    binaryProperties: item.binary ? Object.keys(item.binary) : [],
    binaryDetails: item.binary ? Object.fromEntries(
      Object.entries(item.binary).map(([key, value]) => [
        key,
        {
          fileName: value.fileName,
          mimeType: value.mimeType,
          fileExtension: value.fileExtension,
          dataLength: value.data?.length || 0
        }
      ])
    ) : null
  },
  binary: item.binary
}));

This outputs detailed information about what binary data exists without modifying it.

Debugging Technique 3: Step-by-Step Execution

  1. Disable all nodes except the source
  2. Run and verify binary exists
  3. Enable the next node
  4. Run and verify binary still exists
  5. Repeat until binary disappears
  6. The last enabled node is the culprit

Debugging Technique 4: Expression Testing

Test binary expressions in the expression editor:

// Check if binary exists
{{ $binary ? 'has binary' : 'no binary' }}

// List available properties
{{ $binary ? Object.keys($binary).join(', ') : 'none' }}

// Check specific property
{{ $binary?.data ? 'data exists' : 'data missing' }}

For complex debugging scenarios, our workflow debugger tool can help identify issues.

Real-World Workflow Patterns

These patterns solve common file-handling scenarios in production.

Pattern 1: Email Attachment Processing Pipeline

Scenario: Extract data from PDF invoices received via email and log to a spreadsheet.

Gmail Trigger → IF (has attachment) → Extract from File → Code (parse) → Google Sheets

Gmail Trigger configuration:

  • Trigger: “Message Received”
  • Download Attachments: Enabled

IF node:

{{ $json.attachments?.length > 0 }}

Extract from File:

  • Operation: Extract from PDF
  • Binary Property: attachment_0

Code node (parse invoice):

const text = $json.text;

return [{
  json: {
    invoiceNumber: text.match(/Invoice[#:\s]+(\w+)/i)?.[1] || 'Unknown',
    amount: text.match(/Total[:\s]*\$?([\d,.]+)/)?.[1] || '0',
    date: text.match(/Date[:\s]*(\d{1,2}\/\d{1,2}\/\d{2,4})/)?.[1] || 'Unknown',
    vendor: text.match(/From[:\s]*(.+)/m)?.[1]?.trim() || 'Unknown'
  }
}];

Pattern 2: Form Upload Validation and Storage

Scenario: Accept file uploads, validate them, and store in Google Drive.

Form Trigger → Code (validate) → IF (valid) → Google Drive Upload
                                      ↓ (invalid)
                                 Error Response

Form Trigger:

  • File field named document

Code node (validation):

const item = $input.first();
const binary = item.binary?.document;

if (!binary) {
  return [{ json: { valid: false, error: 'No file uploaded' } }];
}

const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
const maxSize = 10 * 1024 * 1024; // 10MB

if (!allowedTypes.includes(binary.mimeType)) {
  return [{ json: { valid: false, error: 'Invalid file type' } }];
}

// Base64 length is ~1.33x file size
const estimatedSize = (binary.data?.length || 0) * 0.75;
if (estimatedSize > maxSize) {
  return [{ json: { valid: false, error: 'File too large' } }];
}

return [{
  json: {
    valid: true,
    fileName: binary.fileName,
    mimeType: binary.mimeType
  },
  binary: item.binary
}];

Google Drive:

  • Operation: Upload
  • Binary Property: document

For more Google Drive patterns, see our Google Drive upload guide.

Pattern 3: API File Download and Transformation

Scenario: Download Excel reports from an API, transform the data, and save to database.

Schedule Trigger → HTTP Request → Extract from File → Code (transform) → Postgres

HTTP Request:

  • Method: GET
  • URL: API endpoint returning Excel file
  • Authentication as needed

Extract from File:

  • Operation: Extract from XLSX
  • Binary Property: data
  • Header Row: Enabled

Code node (transform):

const items = $input.all();

return items.map(item => ({
  json: {
    // Rename fields to match database schema
    customerId: item.json.customer_id,
    orderTotal: parseFloat(item.json.total) || 0,
    orderDate: new Date(item.json.date).toISOString(),
    status: item.json.status?.toLowerCase() || 'pending'
  }
}));

For Excel-specific handling, see our Excel parsing guide.

Pattern 4: Multi-File Batch Processing

Scenario: Process all CSV files from a folder, aggregate results, and generate a summary report.

Schedule → Read Files (*.csv) → Split In Batches → Extract from File → Aggregate → Convert to File → Email

Read/Write Files from Disk:

  • Operation: Read File(s)
  • File Selector: /data/daily-reports/*.csv

Split In Batches:

  • Batch Size: 10 (prevents memory issues)

Extract from File:

  • Operation: Extract from CSV
  • Header Row: Enabled

Aggregate:

  • Group By: None (combine all)
  • Aggregate: Sum of specific fields

Convert to File:

  • Operation: Convert to XLSX
  • File Name: summary_{{ $now.format('yyyy-MM-dd') }}.xlsx

For more batch processing patterns, see our batch processing guide.

Pro Tips and Best Practices

1. Always Verify Binary Property Names

The most common error source. Before configuring any node that consumes binary data:

  1. Check the source node’s output
  2. Note the exact property name
  3. Use that exact name in configuration

Don’t assume data. Different nodes use different conventions.

2. Extract Early, Transform Freely

If you need to manipulate file content:

Source → Extract from File → [Any number of transforms] → Convert to File (if needed)

Once extracted to JSON, you can use any node without worrying about binary preservation.

3. Use Code Node for Complex Validation

Before processing files in production:

const binary = $input.first().binary?.data;

if (!binary) throw new Error('No binary data');
if (!binary.fileName) throw new Error('Missing filename');
if (!binary.mimeType) throw new Error('Missing MIME type');
if ((binary.data?.length || 0) < 100) throw new Error('File appears empty');

return $input.all();

This catches problems early with clear error messages.

4. Don’t Trust File Extensions Alone

File extensions can be faked. A malicious user could rename malware.exe to document.pdf. For security-sensitive workflows:

const binary = $input.first().binary?.data;

// Check MIME type matches expected format
const allowedMimeTypes = [
  'application/pdf',
  'image/jpeg',
  'image/png',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];

if (!allowedMimeTypes.includes(binary.mimeType)) {
  throw new Error(`Unexpected file type: ${binary.mimeType}`);
}

// For critical workflows, check magic bytes (file signatures)
const buffer = await this.helpers.getBinaryDataBuffer(0, 'data');
const magicBytes = buffer.slice(0, 4).toString('hex');

// PDF starts with "%PDF" (25504446 in hex)
// JPEG starts with FFD8FF
// PNG starts with 89504E47
const signatures = {
  '25504446': 'application/pdf',
  'ffd8ff': 'image/jpeg',
  '89504e47': 'image/png'
};

// Validate magic bytes match claimed MIME type
const detectedType = Object.entries(signatures)
  .find(([sig]) => magicBytes.startsWith(sig.toLowerCase()))?.[1];

if (detectedType && detectedType !== binary.mimeType) {
  throw new Error(`File content doesn't match claimed type`);
}

return $input.all();

This is especially important for user-uploaded files or files from untrusted sources.

5. Enable Filesystem Mode in Production

Even if your files are small, filesystem mode provides safety margin:

N8N_DEFAULT_BINARY_DATA_MODE=filesystem

The performance impact is minimal. The crash prevention is significant.

6. Handle Missing Binary Gracefully

In production workflows, files might not arrive:

// In expressions
{{ $binary?.data?.fileName ?? 'No file provided' }}

// In Code node
const binary = item.binary?.attachment_0;
if (!binary) {
  // Log, notify, or skip instead of crashing
  return { json: { status: 'skipped', reason: 'No attachment' } };
}

7. Document Binary Property Names

In complex workflows, add sticky notes documenting which nodes produce which binary properties. Future you will thank present you.

8. Test with Real Files Early

Sample files often differ from production files. Test with actual customer files as soon as possible to catch format differences, encoding issues, or unexpected structures.

For complex file processing workflows, our workflow development services can help build production-ready solutions. For architectural guidance, explore our consulting services.

Frequently Asked Questions

Why does my binary data disappear after an Edit Fields node?

This happens because Edit Fields in “Manual Mapping” mode (the default) replaces entire items, including binary data. If you don’t explicitly include binary in your mapping, it’s discarded.

Fix: Switch to “Keep All Fields” mode. This preserves all existing data (including binary) and only modifies the fields you specify.

If you need Manual Mapping mode:

  1. Add a binary field mapping
  2. Set it to {{ $binary }} to preserve all binary properties
  3. Or map specific properties: data = {{ $binary.data }}

How do I access binary data from a previous node in the workflow?

Use node reference expressions:

// Access binary from a specific node
{{ $('HTTP Request').item.binary.data }}

// Get the filename from that binary
{{ $('HTTP Request').item.binary.data.fileName }}

// Check if it exists
{{ $('HTTP Request').item.binary?.data ? 'exists' : 'missing' }}

The $('Node Name') syntax references output from any earlier node. The .item gets the current item at the same index. The .binary accesses binary data.

Note: This works for nodes in the same execution branch. For nodes in parallel branches, you may need to use Merge node first.

What’s the difference between filesystem and S3 binary mode?

Filesystem mode:

  • Stores binary data in temporary files on local disk
  • Works for single-instance n8n
  • Files are on the same machine running n8n
  • Good for: Development, single-server production

S3 mode:

  • Stores binary data in S3-compatible cloud storage
  • Required for queue mode with multiple workers
  • All instances access the same storage
  • Good for: Distributed deployments, queue mode, high availability

Use filesystem for simple setups. Use S3 when you have multiple n8n instances or workers that need to share binary data.

How do I handle multiple file attachments in a single workflow?

Email attachments arrive as attachment_0, attachment_1, etc. To process all attachments:

Option 1: Process sequentially Use a Loop Over Items approach:

// Code node to split attachments into separate items
const item = $input.first();
const results = [];

for (const [key, binary] of Object.entries(item.binary || {})) {
  if (key.startsWith('attachment_')) {
    results.push({
      json: {
        attachmentKey: key,
        fileName: binary.fileName
      },
      binary: { data: binary }  // Normalize to 'data' property
    });
  }
}

return results;

Then process each item normally.

Option 2: Process specific attachments If you only need certain attachments, reference them directly:

  • First attachment: Binary Property = attachment_0
  • Second attachment: Binary Property = attachment_1

Can I manipulate binary content directly in n8n (resize images, modify PDFs)?

n8n doesn’t have built-in image or PDF manipulation nodes. However, you have options:

Option 1: External APIs Use HTTP Request to call image processing APIs:

  • Cloudinary for image transformation
  • Adobe PDF Services for PDF manipulation
  • imgix for image resizing

Option 2: Execute Command (self-hosted) For self-hosted n8n with appropriate tools installed:

# ImageMagick for images
convert input.jpg -resize 800x600 output.jpg

# Ghostscript for PDFs
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -o output.pdf input.pdf

Use Read/Write Files to move binary data to disk, Execute Command to process, then read back.

Option 3: Code node with libraries For simple operations, you can use JavaScript in Code node with appropriate libraries. However, complex image/PDF manipulation is better handled externally.

For media processing workflows, see our FFmpeg media processing guide.

Ready to Automate Your Business?

Tell us what you need automated. We'll build it, test it, and deploy it fast.

48-72 Hour Turnaround
Production Ready
Free Consultation

Create Your Free Account

Sign up once, use all tools free forever. We require accounts to prevent abuse and keep our tools running for everyone.

or

You're in!

Check your email for next steps.

By signing up, you agree to our Terms of Service and Privacy Policy. No spam, unsubscribe anytime.

🚀

Get Expert Help

Add your email and one of our n8n experts will reach out to help with your automation needs.

or

We'll be in touch!

One of our experts will reach out soon.

By submitting, you agree to our Terms of Service and Privacy Policy. No spam, unsubscribe anytime.