RAP Logo

function

Execute custom JavaScript code to transform and manipulate data

function Block

The function block allows you to execute custom JavaScript code to transform and manipulate data flowing through your workflows. It's one of the most powerful and flexible blocks for implementing custom logic and data transformations.

Overview

The function block provides a JavaScript execution environment where you can write custom code to process messages, transform data, make calculations, and implement complex business logic that isn't available in other blocks.

Configuration Options

Code Editor

  • JavaScript Code: Write your custom JavaScript function
  • Setup Tab: Initialize variables and import libraries (runs once)
  • On Start Tab: Code that runs when the flow starts
  • On Stop Tab: Code that runs when the flow stops

Function Environment

  • Available Objects:
    • msg: The incoming message object
    • node: Reference to the current node
    • context: Node-specific persistent storage
    • flow: Flow-wide persistent storage
    • global: Global persistent storage
    • env: Environment variables

Return Options

  • Return Message: Use return msg to pass the modified message
  • Send Message: Use node.send(msg) to send message(s)
  • Multiple Outputs: Send different messages to multiple outputs
  • No Output: Perform side effects without sending messages

Basic Function Structure

Simple Transformation

// Transform the payload
msg.payload = msg.payload.toUpperCase();
return msg;

Multiple Operations

// Multiple transformations
msg.payload.processed = true;
msg.payload.timestamp = new Date().toISOString();
msg.payload.count = (msg.payload.count || 0) + 1;
return msg;

Conditional Logic

// Conditional processing
if (msg.payload.type === "urgent") {
  msg.priority = "high";
  msg.payload.escalated = true;
} else {
  msg.priority = "normal";
}
return msg;

Common Use Cases

Data Transformation

Convert data formats and structures:

// Transform array to object
const items = msg.payload.items;
const itemsById = {};
items.forEach((item) => {
  itemsById[item.id] = item;
});
msg.payload = itemsById;
return msg;

Calculations and Processing

Perform mathematical operations:

// Calculate totals
const items = msg.payload.line_items;
let total = 0;
let tax = 0;

items.forEach((item) => {
  const itemTotal = item.quantity * item.price;
  total += itemTotal;
  tax += itemTotal * 0.1; // 10% tax
});

msg.payload.subtotal = total;
msg.payload.tax = tax;
msg.payload.total = total + tax;
return msg;

String Manipulation

Process and clean text data:

// Clean and format text
let text = msg.payload.text;

// Remove extra whitespace
text = text.replace(/\s+/g, " ").trim();

// Extract email addresses
const emails = text.match(/[\w.-]+@[\w.-]+\.\w+/g) || [];

// Extract phone numbers
const phones = text.match(/\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g) || [];

msg.payload = {
  cleaned_text: text,
  extracted_emails: emails,
  extracted_phones: phones,
};
return msg;

Data Validation

Validate incoming data:

// Validate required fields
const required = ["name", "email", "phone"];
const missing = [];

required.forEach((field) => {
  if (!msg.payload[field]) {
    missing.push(field);
  }
});

if (missing.length > 0) {
  msg.payload = {
    error: "Missing required fields",
    missing_fields: missing,
    valid: false,
  };
} else {
  msg.payload.valid = true;
}

return msg;

Array Processing

Work with arrays and collections:

// Filter and sort array
const documents = msg.payload.documents;

// Filter by date range
const startDate = new Date("2024-01-01");
const endDate = new Date("2024-12-31");

const filtered = documents.filter((doc) => {
  const docDate = new Date(doc.date);
  return docDate >= startDate && docDate <= endDate;
});

// Sort by confidence score
const sorted = filtered.sort((a, b) => b.confidence - a.confidence);

msg.payload = {
  total_documents: documents.length,
  filtered_count: sorted.length,
  documents: sorted,
};

return msg;

Advanced Features

Multiple Outputs

Send different messages to different outputs:

// Create different outputs based on conditions
const inputData = msg.payload;

if (inputData.type === "invoice") {
  const invoiceMsg = { ...msg, payload: { type: "invoice", data: inputData } };
  node.send([invoiceMsg, null, null]); // Send to first output
} else if (inputData.type === "receipt") {
  const receiptMsg = { ...msg, payload: { type: "receipt", data: inputData } };
  node.send([null, receiptMsg, null]); // Send to second output
} else {
  const unknownMsg = { ...msg, payload: { type: "unknown", data: inputData } };
  node.send([null, null, unknownMsg]); // Send to third output
}

Context Storage

Store data between messages:

// Get previous count from context
let count = context.get("message_count") || 0;
count++;

// Store updated count
context.set("message_count", count);

// Add count to message
msg.payload.message_number = count;
msg.payload.timestamp = new Date().toISOString();

return msg;

Error Handling

Handle errors gracefully:

try {
  // Risky operation
  const data = JSON.parse(msg.payload.json_string);
  msg.payload = {
    success: true,
    parsed_data: data,
  };
} catch (error) {
  msg.payload = {
    success: false,
    error: error.message,
    original_data: msg.payload.json_string,
  };
}

return msg;

Async Operations

Handle asynchronous operations:

// For async operations, use node.send() instead of return
async function processData() {
  try {
    // Simulate async operation
    await new Promise((resolve) => setTimeout(resolve, 1000));

    msg.payload.processed = true;
    msg.payload.processing_time = "1000ms";

    node.send(msg);
  } catch (error) {
    msg.payload = {
      error: error.message,
      success: false,
    };
    node.send(msg);
  }
}

processData();

Best Practices

  1. Keep it simple: Use built-in blocks when possible, function blocks for custom logic
  2. Error handling: Always include try-catch for risky operations
  3. Performance: Avoid heavy computations that could block the flow
  4. Documentation: Add comments to explain complex logic
  5. Testing: Use debug blocks to test function outputs

Common Patterns

Data Enrichment

// Add metadata to existing data
msg.payload.metadata = {
  processed_at: new Date().toISOString(),
  node_id: node.id,
  flow_name: "Document Processing",
  version: "1.0",
};

Format Conversion

// Convert between formats
const csvData = msg.payload.csv;
const jsonData = csvData.map((row) => ({
  id: row[0],
  name: row[1],
  email: row[2],
  date: new Date(row[3]).toISOString(),
}));
msg.payload = jsonData;

Business Logic

// Implement business rules
const order = msg.payload;
let discount = 0;

if (order.customer_type === "premium") {
  discount = 0.15; // 15% discount
} else if (order.total > 1000) {
  discount = 0.1; // 10% discount for large orders
} else if (order.items.length > 5) {
  discount = 0.05; // 5% discount for bulk orders
}

order.discount_rate = discount;
order.discount_amount = order.total * discount;
order.final_total = order.total - order.discount_amount;

Debugging Tips

  • Use console.log() to debug (output appears in Node-RED logs)
  • Use node.warn() and node.error() for different log levels
  • Check the debug panel for message structure
  • Test with simple inputs first, then increase complexity
  • Use try-catch blocks to handle edge cases

Performance Considerations

  • Avoid blocking operations (use async patterns for heavy work)
  • Don't store large objects in context (use external storage)
  • Process arrays efficiently (consider chunking for very large arrays)
  • Use appropriate data structures for your operations

The function block is most powerful when combined with other processing blocks. Use debug blocks to test your functions and switch blocks for routing based on function results.