Writing Files to OPC UA Server
This tutorial explains how to use the OPC UA File Operation node to write data to files stored on an OPC UA server.
Prerequisites
- An OPC UA server that exposes File objects (TypeDefinition = FileType, ns=0;i=11575)
- A configured OPC UA connection endpoint
- Write permissions on the target file
- Knowledge of the file's NodeId or browse path
Write Modes
The OPC UA File Operation node supports two write modes:
- Write (WriteEraseExisting): Completely replaces the file content with new data
- WriteAppend: Adds new data to the end of the existing file
Basic File Writing
Step 1: Configure the Node
- Drag an OPC UA File Operation node onto your flow
- Configure the following properties:
- Endpoint: Select your OPC UA server connection
- NodeId: Enter the NodeId of the file object (e.g.,
ns=1;s=MyFile) - Mode: Select Write
- Encoding: Select utf8 (for text files)
Step 2: Create a Simple Flow
[ Inject ] → [ Function ] → [ OPC UA File Operation ] → [ Debug ]
Step 3: Prepare Data and Write
Function Node:
msg.payload = "Hello World\nThis is my first file!";
return msg;
Click the inject node button. The file will be created/overwritten on the server.
Output:
msg.size: 35 // Number of bytes written
Writing Different Data Types
Writing Plain Text
Function Node:
msg.payload = "Simple text content";
return msg;
File Operation Configuration:
- Mode: Write
- Encoding: utf8
Writing JSON Data
The node automatically converts objects to JSON strings:
Function Node:
msg.payload = {
temperature: 25.5,
humidity: 60,
timestamp: new Date().toISOString(),
status: "normal"
};
return msg;
File Operation Configuration:
- Mode: Write
- Encoding: utf8
Resulting File Content:
{"temperature":25.5,"humidity":60,"timestamp":"2025-11-24T12:00:00.000Z","status":"normal"}
Writing CSV Data
Function Node:
const headers = "Name,Age,City";
const rows = [
"John,25,Paris",
"Jane,30,London",
"Bob,35,Berlin"
];
msg.payload = [headers, ...rows].join('\n');
return msg;
File Operation Configuration:
- Mode: Write
- Encoding: utf8
Resulting File:
Name,Age,City
John,25,Paris
Jane,30,London
Bob,35,Berlin
Writing Binary Data (Buffer)
For images, PDFs, or other binary files:
Function Node:
// Example: Write a small binary file
msg.payload = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
return msg;
File Operation Configuration:
- Mode: Write
- Encoding: none
Writing Numbers and Booleans
Automatically converted to strings:
Function Node:
msg.payload = 42;
// or
msg.payload = true;
return msg;
Resulting File: 42 or true
Overwriting vs Appending
Overwriting Files (Write Mode)
Completely replaces file content:
[ Timer (every hour) ] → [ Generate Report ] → [ Write File ] → [ Notify ]
Generate Report (Function):
const report = {
timestamp: new Date(),
metrics: {
processed: 1542,
errors: 3,
avgTime: 125
}
};
msg.payload = JSON.stringify(report, null, 2);
return msg;
Write File (OPC UA File Operation):
- Mode: Write
- NodeId:
/ns1:Reports/ns1:hourly.json - Encoding: utf8
Each hour, the file is completely replaced with the new report.
Appending to Files (WriteAppend Mode)
Adds content to the end of existing file:
[ Event Trigger ] → [ Format Log Entry ] → [ Append to Log ] → [ Debug ]
Format Log Entry (Function):
const timestamp = new Date().toISOString();
const level = msg.level || "INFO";
const message = msg.payload;
msg.payload = `${timestamp} - ${level} - ${message}\n`;
return msg;
Append to Log (OPC UA File Operation):
- Mode: WriteAppend
- NodeId:
ns=1;s=SystemLog - Encoding: utf8
Resulting File (after multiple appends):
2025-11-24T09:00:00.000Z - INFO - System started
2025-11-24T09:15:23.000Z - WARN - High memory usage detected
2025-11-24T09:30:45.000Z - ERROR - Connection failed
2025-11-24T10:00:00.000Z - INFO - Backup completed
Dynamic File Selection
Write to different files based on runtime conditions:
Flow:
[ Data Input ] → [ Route by Type ] → [ Write File ] → [ Confirm ]
Route by Type (Function):
const type = msg.payload.type;
const date = new Date().toISOString().split('T')[0];
// Select file based on data type
if (type === 'error') {
msg.nodeId = `/ns1:Logs/ns1:errors-${date}.log`;
} else if (type === 'audit') {
msg.nodeId = `/ns1:Logs/ns1:audit-${date}.log`;
} else {
msg.nodeId = `/ns1:Logs/ns1:general-${date}.log`;
}
// Format the log entry
msg.payload = `${new Date().toISOString()} - ${msg.payload.message}\n`;
return msg;
Write File (OPC UA File Operation):
- Mode: WriteAppend
- NodeId: (leave empty - set by msg.nodeId)
- Encoding: utf8
Working with Different Encodings
Writing UTF-8 Files (Default)
Configuration:
- Encoding: utf8
Suitable for: English, European languages, most modern text files
msg.payload = "Hello World! Héllo Wörld! 你好世界!";
Writing Shift_JIS Files (Japanese)
Configuration:
- Encoding: Shift_JIS
msg.payload = "こんにちは世界"; // Hello World in Japanese
Writing GB2312 Files (Chinese)
Configuration:
- Encoding: GB2312
msg.payload = "你好世界"; // Hello World in Chinese
Dynamic Encoding Based on Content
Configuration:
- Encoding: setbymsg
Function Node:
// Detect and set encoding based on content
const text = msg.payload;
if (/[\u3040-\u309F\u30A0-\u30FF]/.test(text)) {
// Contains Japanese characters
msg.encoding = 'Shift_JIS';
} else if (/[\u4E00-\u9FFF]/.test(text)) {
// Contains Chinese characters
msg.encoding = 'GB2312';
} else {
// Default to UTF-8
msg.encoding = 'utf8';
}
return msg;
Advanced Writing Scenarios
Writing Large Files
The node automatically handles large files by:
- Splitting data into optimized chunks
- Respecting server's MaxByteStringLength settings
- Writing chunks sequentially
Example - Writing Large JSON Export:
// Function node
const largeDataset = [];
for (let i = 0; i < 10000; i++) {
largeDataset.push({
id: i,
value: Math.random() * 100,
timestamp: new Date(Date.now() + i * 1000).toISOString()
});
}
msg.payload = JSON.stringify(largeDataset, null, 2);
return msg;
The node handles the large payload automatically - no special configuration needed!
Writing Configuration Files
Flow:
[ Config UI ] → [ Validate Config ] → [ Write Config ] → [ Apply Settings ]
Validate Config (Function):
// Validate configuration before writing
const config = msg.payload;
if (!config.server || !config.port) {
node.error("Invalid configuration: missing required fields");
return null;
}
// Add metadata
config.lastUpdated = new Date().toISOString();
config.version = "1.0";
msg.payload = JSON.stringify(config, null, 2);
return msg;
Write Config (OPC UA File Operation):
- Mode: Write (replace entire config)
- NodeId:
/ns1:Config/ns1:app-config.json - Encoding: utf8
Conditional Write (Create or Update)
[ Data ] → [ Check File ] → [ Switch ] → [ Write New ]
↓ ↓
[ Debug ] [ Append Existing ]
Check File (OPC UA File Operation #1):
- Mode: ReadSize
Switch Node:
- If msg.payload > 0 (file exists) → route to Append Existing
- If msg.error (file doesn't exist) → route to Write New
Write New (OPC UA File Operation #2):
- Mode: Write
Append Existing (OPC UA File Operation #3):
- Mode: WriteAppend
Backup Before Overwriting
[ New Data ] → [ Read Current ] → [ Save Backup ] → [ Write New ] → [ Success ]
Read Current (OPC UA File Operation #1):
- Mode: Read
- NodeId:
ns=1;s=DataFile
Save Backup (Function):
// Store current content for backup
flow.set('backup', msg.payload);
// Restore original message for write
msg.payload = flow.get('newData');
return msg;
Write New (OPC UA File Operation #2):
- Mode: Write
- NodeId:
ns=1;s=DataFile
Error Handling
Handling Write Errors
// In a Catch node watching the File Operation node
if (msg.error) {
node.warn("File write failed: " + msg.error);
// Take corrective action based on error type
if (msg.error.includes("BadNotWritable")) {
msg.payload = "File is not writable (check permissions)";
} else if (msg.error.includes("BadUserAccessDenied")) {
msg.payload = "Access denied - insufficient permissions";
} else if (msg.error.includes("BadNodeIdUnknown")) {
msg.payload = "File not found on server";
} else {
msg.payload = "Unknown error writing file";
}
// Log to alternative location
flow.set('failedWrites', flow.get('failedWrites') || []);
flow.get('failedWrites').push({
timestamp: new Date(),
data: msg.originalPayload,
error: msg.error
});
return msg;
}
Validating Write Success
// In a node after File Operation
if (msg.size) {
node.log(`Successfully wrote ${msg.size} bytes to file`);
msg.payload = {
success: true,
bytesWritten: msg.size,
timestamp: new Date()
};
} else {
node.warn("Write completed but size unknown");
}
return msg;
Monitoring Node Status
The node status indicator shows:
- "Operating": Writing in progress
- "size = X": Write completed successfully (X bytes written)
- "failed": Write operation failed (check debug panel for details)
Complete Example: Data Logger
Here's a complete example that logs sensor data with rotation:
[ MQTT In ] → [ Parse Data ] → [ Format Entry ] → [ Daily Router ] → [ Write Log ] → [ Notify ]
↓
[ Archive Old ]
Parse Data (Function):
msg.sensorData = JSON.parse(msg.payload);
msg.timestamp = new Date();
return msg;
Format Entry (Function):
const entry = {
timestamp: msg.timestamp.toISOString(),
sensor: msg.sensorData.id,
temperature: msg.sensorData.temp,
humidity: msg.sensorData.humidity,
battery: msg.sensorData.battery
};
msg.payload = JSON.stringify(entry) + '\n';
msg.logDate = msg.timestamp.toISOString().split('T')[0];
return msg;
Daily Router (Function):
// Use date-based file names
msg.nodeId = `/ns1:SensorLogs/ns1:data-${msg.logDate}.jsonl`;
// Check if we need to archive yesterday's file
const lastLogDate = flow.get('lastLogDate');
if (lastLogDate && lastLogDate !== msg.logDate) {
// New day - trigger archive
const archiveMsg = {
nodeId: `/ns1:SensorLogs/ns1:data-${lastLogDate}.jsonl`,
archiveDate: lastLogDate
};
node.send([msg, archiveMsg]);
} else {
node.send([msg, null]);
}
flow.set('lastLogDate', msg.logDate);
return;
Write Log (OPC UA File Operation):
- Mode: WriteAppend
- NodeId: (set by msg.nodeId)
- Encoding: utf8
Tips and Best Practices
-
Choose the Right Mode:
- Use Write for complete file replacement (configs, reports)
- Use WriteAppend for logging and incremental data
-
Validate Input Data:
- Always validate msg.payload before writing
- Handle undefined, null, or empty values
- Check data types match expected format
-
Handle Errors Gracefully:
- Add Catch nodes for all write operations
- Implement retry logic for transient failures
- Store failed writes for manual review
-
Consider File Size:
- The node handles chunking automatically
- Monitor disk space on the OPC UA server
- Implement log rotation for continuous logging
-
Use Atomic Writes:
- Write to a temporary file first
- Rename/move after successful write
- Prevents corruption if write fails mid-operation
-
Encoding Matters:
- Always specify encoding for text files
- Use
nonefor binary data - Match encoding to target system requirements
-
Security:
- Verify write permissions before operations
- Never write user input directly without validation
- Log all write operations for audit trails
Related Operations
- Reading Files - Learn how to read data from files
- Appending to Files - More details on append operations
- Browse Files - Discover available files on the server
Troubleshooting
Problem: "nothing to write" error
Solution: Ensure msg.payload is not undefined or empty. Check your function node output.
Problem: "BadNotWritable" status code
Solution: The file or server doesn't allow writes. Check:
- File permissions on the OPC UA server
- User access rights in your OPC UA connection
- File is not locked by another process
Problem: Written file contains "[object Object]"
Solution: You're writing an object without JSON.stringify. Either:
- Let the node auto-stringify (it should do this automatically)
- Manually stringify in a function node:
msg.payload = JSON.stringify(msg.payload)
Problem: Special characters appear corrupted
Solution: Wrong encoding. Try:
- Check the source encoding of your data
- Match the encoding setting to your target system
- Use UTF-8 for international characters
Problem: Write succeeds but file is empty
Solution: Check that msg.payload actually contains data:
- Add a Debug node before the File Operation node
- Verify msg.payload is not null, undefined, or empty string
- Check if payload type is supported (String, Buffer, Object, Number, Boolean)