TopFlow
LearnBuildSecurity
Data TransformationP0 - Critical

JavaScript Node

Execute custom JavaScript code to transform data, parse JSON, extract fields, and implement business logic between workflow nodes. Sandboxed execution ensures security.

Sandboxed Execution: JavaScript code runs in a limited scope with no access to global objects, file system, or network. Only input variables and the return value are accessible.

Overview

The JavaScript node is one of the most versatile nodes in TopFlow. It enables you to write custom JavaScript code that runs during workflow execution, allowing you to transform data, implement business logic, parse complex responses, and manipulate data structures between nodes.

Common Use Cases
1
JSON Parsing & Field Extraction - Extract specific fields from complex API responses, nested objects, or arrays
2
Data Transformation - Convert data formats, restructure objects, merge data from multiple sources
3
Calculations & Aggregations - Perform math operations, calculate statistics, sum values, compute averages
4
String Manipulation - Format text, concatenate strings, extract substrings, apply templates
5
Business Logic - Implement custom validation rules, scoring algorithms, risk calculations
6
Array Operations - Filter arrays, map values, reduce data, sort elements

Configuration

Required Parameters
These fields must be configured for the node to execute
codestring

JavaScript code to execute. Code must return a value using the return keyword. Inputs from upstream nodes are available as variables: input1, input2, input3, etc.

Tip: The code editor in TopFlow provides syntax highlighting and basic error detection. Use console.log() statements for debugging (output appears in execution logs).
Example Code:
// Parse JSON and extract specific field
const data = JSON.parse(input1)
return data.user.email

// Calculate severity score
const score = (input1.critical * 10) + (input1.high * 5)
return score >= 50 ? "HIGH" : "MEDIUM"

// Transform array
const threats = JSON.parse(input1)
return threats
  .filter(t => t.severity === "CRITICAL")
  .map(t => t.title)
  .join(", ")

Input & Output Specification

Input Variables
How upstream node outputs become available in your code

When nodes connect to the JavaScript node, their outputs become available as numbered input variables:

input1Output from the first upstream node (leftmost connection)
input2Output from the second upstream node
input3Output from the third upstream node
...And so on for additional connections
Input Ordering: Inputs are numbered based on the X position (left-to-right) of the source nodes on the canvas. The leftmost connected node becomes input1.
Output Value
What downstream nodes receive

Your JavaScript code must return a value using the return keyword. This value becomes the output that downstream nodes can access.

Supported Return Types:
  • String - Text data, JSON strings, formatted output
  • Number - Integers, floats, calculated values
  • Boolean - true/false for conditional logic
  • Object - Structured data with multiple fields
  • Array - Lists of values or objects
  • null/undefined - Empty or missing values
Example Return Values:
// Return string
return "HIGH"

// Return number
return 42

// Return object
return {
  severity: "CRITICAL",
  count: 5,
  timestamp: new Date().toISOString()
}

// Return array
return ["threat1", "threat2", "threat3"]

// Return boolean
return score >= 50

Node Data Interface

TypeScript interface defining the JavaScript node's configuration and execution state:

export type JavaScriptNodeData = {
  // Configuration
  code: string  // JavaScript code to execute

  // Execution state (managed by system)
  status?: "idle" | "running" | "completed" | "error"
  output?: any  // Return value from executed code
  error?: string
}

Usage Examples

Example 1: Parse JSON and Extract Fields
Extract specific fields from API response
Scenario:

HTTP Request node returns complex JSON. You need to extract just the email addresses.

Input (from HTTP Request node):
{
  "status": 200,
  "data": {
    "users": [
      { "id": 1, "name": "Alice", "email": "alice@example.com" },
      { "id": 2, "name": "Bob", "email": "bob@example.com" }
    ]
  }
}
JavaScript Code:
// Parse the HTTP response and extract emails
const response = input1
const users = response.data.users

// Map to just email addresses
const emails = users.map(user => user.email)

// Return as comma-separated string
return emails.join(", ")
Output:
"alice@example.com, bob@example.com"
Example 2: Calculate Threat Severity Score
Compute risk score from multiple factors
Scenario:

Text Model node analyzes security events and returns threat counts. Calculate a severity score to determine alerting priority.

Input (from Text Model node):
{
  "critical": 2,
  "high": 5,
  "medium": 10,
  "low": 20
}
JavaScript Code:
// Parse threat counts
const threats = JSON.parse(input1)

// Calculate weighted severity score
// Critical: 10 points, High: 5 points, Medium: 2 points, Low: 1 point
const score =
  (threats.critical * 10) +
  (threats.high * 5) +
  (threats.medium * 2) +
  (threats.low * 1)

// Determine severity level
let severity
if (score >= 50) severity = "CRITICAL"
else if (score >= 25) severity = "HIGH"
else if (score >= 10) severity = "MEDIUM"
else severity = "LOW"

// Return structured result
return {
  score: score,
  severity: severity,
  requires_immediate_action: score >= 50,
  threat_summary: `${threats.critical} critical, ${threats.high} high, ${threats.medium} medium, ${threats.low} low`
}
Output:
{
  "score": 65,
  "severity": "CRITICAL",
  "requires_immediate_action": true,
  "threat_summary": "2 critical, 5 high, 10 medium, 20 low"
}
Example 3: Merge Data from Multiple Sources
Combine outputs from multiple upstream nodes
Scenario:

Two parallel HTTP Request nodes fetch user data and threat data. Merge them into a single report.

Inputs:
// input1 - User data from API
{
  "user_id": "123",
  "username": "john.doe",
  "department": "Engineering"
}

// input2 - Threat data from security system
{
  "threats_detected": 3,
  "last_incident": "2024-01-15",
  "risk_level": "HIGH"
}
JavaScript Code:
// Parse both inputs
const userData = JSON.parse(input1)
const threatData = JSON.parse(input2)

// Merge into comprehensive report
return {
  report_timestamp: new Date().toISOString(),
  user: {
    id: userData.user_id,
    name: userData.username,
    department: userData.department
  },
  security: {
    threat_count: threatData.threats_detected,
    last_incident: threatData.last_incident,
    risk_level: threatData.risk_level,
    requires_review: threatData.threats_detected > 0
  },
  summary: `User ${userData.username} has ${threatData.threats_detected} threats detected. Risk level: ${threatData.risk_level}`
}
Output:
{
  "report_timestamp": "2024-01-15T10:30:00.000Z",
  "user": {
    "id": "123",
    "name": "john.doe",
    "department": "Engineering"
  },
  "security": {
    "threat_count": 3,
    "last_incident": "2024-01-15",
    "risk_level": "HIGH",
    "requires_review": true
  },
  "summary": "User john.doe has 3 threats detected. Risk level: HIGH"
}
Example 4: Filter and Transform Arrays
Process lists of items from upstream nodes
Scenario:

HTTP Request node returns list of CVEs. Filter to only critical vulnerabilities affecting your systems.

Input (from HTTP Request node):
{
  "threats": [
    {
      "id": "CVE-2024-1234",
      "severity": "CRITICAL",
      "cvss": 9.8,
      "affected_systems": ["web-server-01", "api-gateway"]
    },
    {
      "id": "CVE-2024-5678",
      "severity": "MEDIUM",
      "cvss": 6.5,
      "affected_systems": ["marketing-site"]
    },
    {
      "id": "CVE-2024-9012",
      "severity": "CRITICAL",
      "cvss": 9.1,
      "affected_systems": ["gitlab-enterprise"]
    }
  ]
}
JavaScript Code:
// Parse threat data
const data = JSON.parse(input1)

// Filter to critical threats only
const criticalThreats = data.threats.filter(threat =>
  threat.severity === "CRITICAL"
)

// Transform to simplified format
const summary = criticalThreats.map(threat => ({
  cve: threat.id,
  score: threat.cvss,
  systems: threat.affected_systems.join(", "),
  priority: threat.cvss >= 9.5 ? "URGENT" : "HIGH"
}))

// Return count and details
return {
  critical_count: criticalThreats.length,
  threats: summary,
  alert_message: `⚠️ ${criticalThreats.length} critical vulnerabilities detected requiring immediate attention`
}
Output:
{
  "critical_count": 2,
  "threats": [
    {
      "cve": "CVE-2024-1234",
      "score": 9.8,
      "systems": "web-server-01, api-gateway",
      "priority": "URGENT"
    },
    {
      "cve": "CVE-2024-9012",
      "score": 9.1,
      "systems": "gitlab-enterprise",
      "priority": "HIGH"
    }
  ],
  "alert_message": "⚠️ 2 critical vulnerabilities detected requiring immediate attention"
}
Example 5: Format Data for Downstream Services
Transform data into specific format required by external APIs
Scenario:

Text Model generates security analysis. Format it for Slack notification with proper markdown and emoji.

Input (from Text Model node):
{
  "event_type": "Suspicious Login",
  "severity": "HIGH",
  "user": "john.doe",
  "ip_address": "203.0.113.42",
  "country": "Unknown",
  "timestamp": "2024-01-15T10:30:00Z",
  "recommendation": "Force password reset and review account activity"
}
JavaScript Code:
// Parse security event
const event = JSON.parse(input1)

// Map severity to emoji
const severityEmoji = {
  "CRITICAL": "🔴",
  "HIGH": "🟠",
  "MEDIUM": "🟡",
  "LOW": "🟢"
}

// Format timestamp
const date = new Date(event.timestamp)
const formattedTime = date.toLocaleString("en-US", {
  month: "short",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  timeZoneName: "short"
})

// Build Slack-formatted message
return {
  text: `${severityEmoji[event.severity]} Security Alert: ${event.event_type}`,
  blocks: [
    {
      type: "header",
      text: {
        type: "plain_text",
        text: `${severityEmoji[event.severity]} Security Alert: ${event.event_type}`
      }
    },
    {
      type: "section",
      fields: [
        { type: "mrkdwn", text: `*Severity:*\n${event.severity}` },
        { type: "mrkdwn", text: `*User:*\n${event.user}` },
        { type: "mrkdwn", text: `*IP Address:*\n${event.ip_address}` },
        { type: "mrkdwn", text: `*Time:*\n${formattedTime}` }
      ]
    },
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: `*Recommendation:*\n${event.recommendation}`
      }
    }
  ]
}
Output (ready for Slack webhook):
{
  "text": "🟠 Security Alert: Suspicious Login",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "🟠 Security Alert: Suspicious Login"
      }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Severity:*\nHIGH" },
        { "type": "mrkdwn", "text": "*User:*\njohn.doe" },
        { "type": "mrkdwn", "text": "*IP Address:*\n203.0.113.42" },
        { "type": "mrkdwn", "text": "*Time:*\nJan 15, 10:30 AM UTC" }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Recommendation:*\nForce password reset and review account activity"
      }
    }
  ]
}

Security Considerations

Sandboxed Execution: JavaScript code runs in a limited scope using new Function(). While this provides basic isolation, it's not a full security sandbox. Avoid executing untrusted code.
Execution Limitations
What's available and what's not in your JavaScript code
✓ Available:
  • Input variables - input1, input2, etc.
  • Standard JavaScript features - Array methods, String methods, Object methods, Math functions
  • JSON operations - JSON.parse(), JSON.stringify()
  • Date/Time - new Date(), date formatting
  • console.log() - For debugging (appears in execution logs)
✗ Not Available:
  • File system access - No fs module, can't read/write files
  • Network access - No fetch(), XMLHttpRequest, or HTTP clients
  • Global objects - No window, document, process, require()
  • Async operations - No setTimeout(), setInterval(), Promise (code runs synchronously)
  • External modules - No import or require()
Known Limitation: The JavaScript node uses new Function() which still has access to closures and the outer scope. This is NOT a fully secure sandbox. Do not execute untrusted or user-provided code.
Best Practices for Secure Code
Validate inputs

Always validate input data before processing. Check for null/undefined, expected data types, and required fields.

Handle errors gracefully

Use try-catch blocks for operations that might fail (JSON parsing, property access on undefined objects).

Avoid infinite loops

JavaScript execution has a timeout (30 seconds). Infinite loops will cause workflow failure.

Keep code focused

Each JavaScript node should do one thing well. Break complex logic into multiple nodes for better debugging and reusability.

Validation Rules

The JavaScript node is validated before execution. Validation errors will block workflow execution.

Errors (Block Execution)
✕Missing required field: code
✕Empty code field (no JavaScript provided)
✕Syntax error in JavaScript code (detected by parser)
✕Code references undefined input variables (e.g., input3 with only 2 connections)
Warnings (Don't Block)
⚠No return statement found (code will return undefined)
⚠Code is very long (>500 lines) - Consider breaking into multiple nodes
⚠Code contains potentially expensive operations (nested loops with large arrays)
⚠No upstream connections (no input variables available)

Code Generation

When you export your workflow to TypeScript code, the JavaScript node's code is embedded directly into the generated function:

Generated Code (Workflow Function Export)
// JavaScript Node: Calculate severity score
const node_javascript1 = (() => {
  // Your JavaScript code is inserted here
  const threats = JSON.parse(input1)

  const score =
    (threats.critical * 10) +
    (threats.high * 5) +
    (threats.medium * 2) +
    (threats.low * 1)

  let severity
  if (score >= 50) severity = "CRITICAL"
  else if (score >= 25) severity = "HIGH"
  else if (score >= 10) severity = "MEDIUM"
  else severity = "LOW"

  return {
    score: score,
    severity: severity,
    requires_immediate_action: score >= 50
  }
})()

// Result is available for downstream nodes
const javascript1_result = node_javascript1
Generated Code (Route Handler Export)
export async function POST(req: Request) {
  // ... previous nodes ...

  // JavaScript Node: Calculate severity score
  try {
    const node_javascript1 = (() => {
      // Input variables from upstream nodes
      const input1 = node_textModel1

      // Your JavaScript code
      const threats = JSON.parse(input1)

      const score =
        (threats.critical * 10) +
        (threats.high * 5) +
        (threats.medium * 2) +
        (threats.low * 1)

      let severity
      if (score >= 50) severity = "CRITICAL"
      else if (score >= 25) severity = "HIGH"
      else if (score >= 10) severity = "MEDIUM"
      else severity = "LOW"

      return {
        score: score,
        severity: severity,
        requires_immediate_action: score >= 50
      }
    })()

    const javascript1_result = node_javascript1
  } catch (error) {
    return Response.json(
      { error: `JavaScript execution failed: ${error.message}` },
      { status: 500 }
    )
  }

  // ... downstream nodes ...

  return Response.json({
    success: true,
    outputs: { javascript1: javascript1_result }
  })
}

Best Practices

Do
✓Always return a value using the return keyword so downstream nodes have data to work with
✓Validate inputs before processing - Check for null/undefined and expected data types
✓Use try-catch for error handling when parsing JSON or accessing nested properties
✓Keep code focused and simple - One JavaScript node should do one thing well
✓Use console.log() for debugging - Output appears in execution logs during testing
✓Add comments explaining complex logic - Future you (and team members) will thank you
✓Return structured objects for complex data rather than concatenated strings
Don't
✕Don't use async/await or Promises - Code runs synchronously only
✕Don't create infinite loops - Workflow execution will timeout after 30 seconds
✕Don't access global objects - window, document, process are not available
✕Don't execute untrusted code - The sandbox is not fully secure
✕Don't put all logic in one massive node - Break complex workflows into multiple JavaScript nodes
✕Don't forget to test with real data - Validation passes but runtime errors are common
✕Don't reference undefined inputs - Check how many nodes are connected before using input3
Common Code Patterns
Safe JSON Parsing:
try {
  const data = JSON.parse(input1)
  return data.someField
} catch (error) {
  console.log("JSON parse error:", error.message)
  return null
}
Safe Property Access:
// Use optional chaining and nullish coalescing
const email = data?.user?.email ?? "unknown@example.com"
const count = data?.threats?.length ?? 0
Input Validation:
if (!input1 || typeof input1 !== "string") {
  return { error: "Invalid input: expected string" }
}

const data = JSON.parse(input1)
if (!data.threats || !Array.isArray(data.threats)) {
  return { error: "Invalid data structure" }
}

// Proceed with processing...

Related Nodes

Conditional Node

Branch workflow based on conditions. Use JavaScript node to calculate values, then Conditional node to decide the path.

View docs
HTTP Request Node

Fetch external data via APIs. Use JavaScript node to parse and transform the API responses.

View docs
Structured Output Node

Extract structured data from AI model outputs. JavaScript node provides more flexibility for custom schemas.

View docs
Next Steps
Workflow Patterns

Learn common patterns for data transformation and processing in multi-node workflows

Workflows 101

Understand how data flows between nodes and how to use variable interpolation effectively

Conditional Node

Combine JavaScript calculations with conditional branching for dynamic workflows