Script Task ⚡
Executes JavaScript or Python code directly within the workflow.
Node type: scriptTask
Category: Logic
Actor: scriptTask (2 threads)
Description
The Script Task runs arbitrary code for data transformation, computation, custom logic, or string manipulation. Scripts are executed in an isolated GraalVM Polyglot sandbox — they cannot access the file system, network, or JVM classes directly.
Scripts receive all current workflow variables as inputs and can set new variables or override existing ones by returning a result object.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
scriptFormat | select | Yes | javascript or python |
script | code | Yes | The script body to execute |
Script Environment
Available Variables
All current workflow variables are injected into the script execution context:
// All these are automatically available:
console.log(customerName); // workflow variable {customerName}
console.log(orderTotal); // workflow variable {orderTotal}
console.log(items); // workflow variable {items} (if it's an array/object)
Returning Results
Return an object to set or update workflow variables:
return {
formattedTotal: `$${parseFloat(orderTotal).toFixed(2)}`,
itemCount: items.length,
isLargeOrder: parseFloat(orderTotal) > 1000
};
After this script runs, {formattedTotal}, {itemCount}, and {isLargeOrder} are available to all downstream nodes.
If the script returns null or nothing, no variables are modified.
JavaScript Examples
String Manipulation
// Normalize phone number to E.164 format
const digits = phoneNumber.replace(/\D/g, '');
const e164 = digits.startsWith('1') ? `+${digits}` : `+1${digits}`;
return {
normalizedPhone: e164,
phoneValid: e164.length === 12
};
Date Formatting
// Format a date for email display
const date = new Date(appointmentTimestamp);
const formatted = date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
return { formattedDate: formatted };
Data Transformation
// Transform API response array into summary stats
const orders = JSON.parse(ordersJson);
const total = orders.reduce((sum, o) => sum + o.amount, 0);
const avgOrder = total / orders.length;
const highestOrder = Math.max(...orders.map(o => o.amount));
return {
orderCount: orders.length,
totalRevenue: total.toFixed(2),
averageOrder: avgOrder.toFixed(2),
highestOrder: highestOrder.toFixed(2)
};
Conditional Processing
// Parse AI classification response and validate
let classification;
try {
const parsed = JSON.parse(aiResponse);
classification = parsed.classification;
} catch (e) {
// Fallback if AI returned non-JSON
classification = 'general';
}
const validClasses = ['billing', 'technical', 'account', 'general'];
const isValid = validClasses.includes(classification);
return {
classification: isValid ? classification : 'general',
classificationConfident: isValid
};
Python Examples
# Calculate order discount
discount_rate = 0.15 if float(orderTotal) > 500 else 0.05
discount_amount = float(orderTotal) * discount_rate
final_total = float(orderTotal) - discount_amount
return {
"discountRate": discount_rate,
"discountAmount": round(discount_amount, 2),
"finalTotal": round(final_total, 2)
}
# Parse and validate email list
emails = recipientEmails.split(',')
valid_emails = [e.strip() for e in emails if '@' in e and '.' in e.split('@')[-1]]
return {
"validEmails": valid_emails,
"emailCount": len(valid_emails),
"hasValidEmails": len(valid_emails) > 0
}
Sandbox Restrictions
Scripts run in GraalVM Polyglot sandbox with these restrictions:
| Capability | Available? |
|---|---|
| Standard math/string/array operations | ✅ |
| JSON.parse / JSON.stringify | ✅ |
| Date operations | ✅ |
| Regular expressions | ✅ |
console.log (captured in execution logs) | ✅ |
| File system access | ❌ |
| Network calls (fetch, http) | ❌ |
| JVM class access | ❌ |
require() / import (external modules) | ❌ |
For network calls or external integrations, use the appropriate Service Task nodes instead.
Error Handling
| Scenario | Behavior |
|---|---|
| Script throws an uncaught exception | Node fails, error message in {_error}, takes errorFlow |
| Script returns non-object | No variables modified, execution continues |
| Script syntax error | Node fails immediately at execution start |
| Timeout exceeded | Takes timeoutFlow; script is interrupted |
// Good pattern: wrap risky operations in try/catch
try {
const data = JSON.parse(rawApiResponse);
return { parsedData: data, parseSuccess: true };
} catch (e) {
return { parsedData: null, parseSuccess: false, parseError: e.message };
}
Connections
| Condition | Connection |
|---|---|
| Script completed without error | successFlow or sequenceFlow |
| Script threw uncaught exception | errorFlow |
| Timeout | timeoutFlow |
Best Practices
- Wrap risky operations (JSON.parse, type conversions) in try/catch
- Always return an object — this makes variable changes explicit
- Set a timeout (5–10 seconds) — infinite loops will block the actor thread
- Log intermediate values with
console.log()— they appear in execution logs - Keep scripts focused on one transformation — chain multiple Script Tasks for complex processing
- Test scripts locally before deploying (they're pure functions with simple inputs)