n8n Binary Data: The Complete Guide to Working with Files in Workflows
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:
| Source | Default Binary Property Name |
|---|---|
| HTTP Request | data |
| Read/Write Files from Disk | data |
| 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:
- Click on the node that outputs binary data
- Look at the output panel
- Switch to the Binary tab
- 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
| Node | Binary Property | Notes |
|---|---|---|
| HTTP Request | data | Automatic for binary responses |
| Gmail Trigger | attachment_0, attachment_1 | Numbered sequentially |
| Microsoft Outlook Trigger | attachment_0, attachment_1 | Same pattern as Gmail |
| Google Drive | data | Download operation only |
| Dropbox | data | Download operation only |
| Read/Write Files | data | Single or multiple files |
| Form Trigger | Form field name | Matches your field configuration |
| Webhook | Varies | Check output panel |
| FTP | data | Download operation |
Accessing Binary Data: Three Methods
Once binary data exists in your workflow, you have three ways to work with it.
Method 1: Edit Fields Node (Recommended for Most Cases)
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:
- Add an Edit Fields node
- Enable “Include Other Input Fields” (this preserves JSON)
- 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:
- Click the mode selector (top of Edit Fields panel)
- Select “Keep All Fields”
- Add only the fields you want to change
- 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:
- Run your workflow step by step
- After each node, check the output panel’s Binary tab
- 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 Format | Target Format | Node Sequence |
|---|---|---|
| PDF file | JSON text | Extract from File (PDF operation) |
| Excel file | JSON rows | Extract from File (XLSX operation) |
| CSV file | JSON rows | Extract from File (CSV operation) |
| JSON data | CSV file | Convert to File (CSV operation) |
| JSON data | Excel file | Convert to File (XLSX operation) |
| JSON data | JSON file | Convert to File (JSON operation) |
| XML file | JSON data | Extract from File (Text) → XML node |
| JSON data | XML file | XML 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 Mode | Reliable File Size | Notes |
|---|---|---|
| Default (memory) | Up to 10-15MB | Safe for typical documents |
| Filesystem mode | Up to 100MB+ | Limited by disk space and timeout |
| S3 mode | 500MB+ | 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
| Variable | Default | Purpose |
|---|---|---|
N8N_DEFAULT_BINARY_DATA_MODE | default (memory) | Primary storage mode |
N8N_AVAILABLE_BINARY_DATA_MODES | default | Available 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:
- Main instance receives file upload
- Binary data writes to S3
- Worker picks up job
- 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 Message | Likely Cause | Solution |
|---|---|---|
| ”Binary file ‘data’ not found” | Wrong property name | Check actual property name in output panel |
| ”No binary data received” | Upstream node didn’t output binary | Verify source node configuration |
| ”The item has no binary field” | Binary lost in intermediate node | Check node between source and error |
| ”JavaScript heap out of memory” | File too large for memory mode | Enable filesystem mode |
| Empty extraction results | Wrong file format or corrupted file | Verify file type matches operation |
Debugging Technique 1: Binary Tab Inspection
After every node that should have binary data:
- Run the workflow up to that point
- Click the node
- Look for the Binary tab in the output panel
- 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
- Disable all nodes except the source
- Run and verify binary exists
- Enable the next node
- Run and verify binary still exists
- Repeat until binary disappears
- 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:
- Check the source node’s output
- Note the exact property name
- 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:
- Add a binary field mapping
- Set it to
{{ $binary }}to preserve all binary properties - 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.