Skip to main content

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

  1. Drag an OPC UA File Operation node onto your flow
  2. 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

  1. Choose the Right Mode:

    • Use Write for complete file replacement (configs, reports)
    • Use WriteAppend for logging and incremental data
  2. Validate Input Data:

    • Always validate msg.payload before writing
    • Handle undefined, null, or empty values
    • Check data types match expected format
  3. Handle Errors Gracefully:

    • Add Catch nodes for all write operations
    • Implement retry logic for transient failures
    • Store failed writes for manual review
  4. Consider File Size:

    • The node handles chunking automatically
    • Monitor disk space on the OPC UA server
    • Implement log rotation for continuous logging
  5. Use Atomic Writes:

    • Write to a temporary file first
    • Rename/move after successful write
    • Prevents corruption if write fails mid-operation
  6. Encoding Matters:

    • Always specify encoding for text files
    • Use none for binary data
    • Match encoding to target system requirements
  7. Security:

    • Verify write permissions before operations
    • Never write user input directly without validation
    • Log all write operations for audit trails

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)