16. Script Tasks (Dynamic Calculations)
Script Tasks execute inline JavaScript code during process execution. They enable workflow designers to perform dynamic calculations, data transformations, and date operations without requiring Java code changes.
How It Works
Script Tasks run on the server (Flowable engine) using a custom GraalJS configuration. They access and modify process variables via the execution object.
| Aspect | Details |
|---|---|
| Engine | GraalJS (via super-js identifier) |
| Runs on | Server (Flowable engine) |
| Access | execution.getVariable(), execution.setVariable() |
| Platform helpers | wf.* functions (see below) |
| Format | scriptFormat="super-js" (Required) |
[!IMPORTANT] You MUST use
scriptFormat="super-js"for your Script Tasks. The standardjavascriptorjsformats may not have access to theexecutionobject due to strict security settings.
[!CAUTION] Security Notice: Script Tasks execute on the server with full access to the Java runtime. This means scripts can access any Java class on the classpath, including file system, network, and system operations. The platform assumes the BPMN author is a trusted administrator. Only users with process deployment permissions (ROLE_ADMIN) should be allowed to create or modify Script Tasks. The platform takes no responsibility for damage caused by custom scripts. Review all Script Task code before deploying to production.
Platform Helpers (wf.*)
Script Tasks have access to the wf object — a comprehensive set of platform functions that provide access to Spring-managed data (users, roles, process history) and formatting utilities (dates, numbers, HTML generation) that are otherwise unavailable from pure JavaScript.
[!IMPORTANT] Process-aware functions (Section 5 below) require the
executionobject as their first parameter. This is the sameexecutionobject designers already use forexecution.getVariable(). If omitted, these methods return empty/null (null-safe). All other functions do NOT requireexecution.
1. User & Organization
| Function | Returns | Description |
|---|---|---|
wf.getUserFullName(email) |
String |
Resolves email to full name. Returns email if not found. |
wf.getUsersByRole(roleName) |
List<String> |
All emails belonging to a role (e.g., "ROLE_MANAGER"). |
wf.getUserCountByRole(roleName) |
int |
Number of users in a role. Useful for conditional logic. |
wf.getRoleDisplayName(roleName) |
String |
"ROLE_MANAGER" → "Manager". Strips prefix, capitalizes. |
wf.getAllRoles() |
List<String> |
All role names in the system. |
2. Date & Time
| Function | Returns | Description |
|---|---|---|
wf.now() |
Date |
Current server date/time. |
wf.today() |
String |
Today's date formatted as dd/MM/yyyy. |
wf.formatDate(date, pattern) |
String |
Format a date (e.g., "dd MMMM yyyy" → "19 April 2026"). |
wf.formatDateTime(date, pattern) |
String |
Same as formatDate — exists for designer clarity. |
wf.addDays(date, n) |
Date |
Add (or subtract with negative) calendar days to a date. |
wf.addBusinessDays(date, n) |
Date |
Add working days (skip weekends). |
wf.daysBetween(start, end) |
int |
Calendar days between two dates. |
wf.businessDaysBetween(start, end) |
int |
Working days between two dates (excludes weekends). |
wf.isWeekend(date) |
boolean |
Is this date a Saturday or Sunday? |
wf.isOverdue(date) |
boolean |
Is this date in the past? |
wf.getYear(date) |
int |
Extract year from a date. Useful for document numbering. |
Date Pattern Quick Reference
formatDate and formatDateTime accept standard Java SimpleDateFormat patterns. Use the following tokens to build the format string you need:
| Token | Meaning | Example |
|---|---|---|
d |
Day of month (no leading zero) | 2 |
dd |
Day of month (leading zero) | 02 |
M |
Month number (no leading zero) | 5 |
MM |
Month number (leading zero) | 05 |
MMM |
Month abbreviated | May |
MMMM |
Month full name | May |
yy |
Year 2-digit | 26 |
yyyy |
Year 4-digit | 2026 |
HH |
Hour 24h (leading zero) | 21 |
hh |
Hour 12h (leading zero) | 09 |
mm |
Minutes (leading zero) | 55 |
ss |
Seconds (leading zero) | 07 |
a |
AM/PM marker | PM |
Common ready-to-use patterns:
| Pattern | Output |
|---|---|
dd/MM/yyyy |
02/05/2026 |
dd.MM.yyyy |
02.05.2026 |
yyyy-MM-dd |
2026-05-02 |
dd MMMM yyyy |
02 May 2026 |
dd MMM yyyy |
02 May 2026 |
dd/MM/yyyy HH:mm |
02/05/2026 21:55 |
dd MMMM yyyy HH:mm |
02 May 2026 21:55 |
yyyy-MM-dd HH:mm:ss |
2026-05-02 21:55:07 |
hh:mm a |
09:55 PM |
Tip: You can nest
wf.addDays()orwf.addBusinessDays()directly insidewf.formatDate():wf.formatDate(wf.addBusinessDays(wf.now(), 5), 'dd MMMM yyyy') // → "09 May 2026" (5 business days from today)
3. Number & Currency Formatting
| Function | Returns | Description |
|---|---|---|
wf.formatNumber(number, decimals) |
String |
Format with fixed decimals: 1234.5 → "1,234.50". |
wf.formatCurrency(number, symbol) |
String |
formatCurrency(1500.00, "₺") → "₺1,500.00". |
wf.formatPercent(number) |
String |
0.85 → "85%". |
wf.numberToWordsEN(number) |
String |
1500 → "One Thousand Five Hundred". |
wf.numberToWordsTR(number) |
String |
1500 → "Bin Beş Yüz". Turkish rules applied. |
wf.round(number, decimals) |
double |
Round to N decimal places. |
wf.sum(jsonArray, field) |
double |
Sum a numeric field across a JSON array. |
wf.average(jsonArray, field) |
double |
Average a numeric field across a JSON array. |
4. String & Text
| Function | Returns | Description |
|---|---|---|
wf.capitalize(str) |
String |
"hello world" → "Hello world". |
wf.titleCase(str) |
String |
"john doe" → "John Doe". |
wf.truncate(str, maxLen) |
String |
Truncate with ellipsis: "Long text...". |
wf.padLeft(str, len, char) |
String |
padLeft("42", 6, "0") → "000042". |
wf.nl2br(str) |
String |
Convert newlines to <br/> tags. Essential for textarea content in PDFs. |
wf.escapeHtml(str) |
String |
Escape <>&"' for safe HTML embedding. |
wf.stripHtml(str) |
String |
Remove all HTML tags from a string. |
⚠️ Security: Always use
wf.escapeHtml()when embedding user input into PDF templates via${var}placeholders. Without it, a user could inject HTML/CSS into the PDF via a form field.
5. Process & Workflow (require execution)
| Function | Returns | Description |
|---|---|---|
wf.getProcessName(execution) |
String |
Human-readable name of the current process definition. |
wf.getProcessStartDate(execution) |
Date |
When the current process instance was started. |
wf.getCompletedTaskUser(execution, taskDefinitionKey) |
String |
Email of who completed a specific past task. |
wf.getCompletedTaskDate(execution, taskDefinitionKey) |
Date |
When a specific past task was completed. |
wf.getProcessAuditTrail(execution) |
List<Map> |
Full structured history: taskName, completedBy, completedDate for every completed task. |
6. HTML Builders (PDF Power Tools)
These functions turn raw JSON data into formatted HTML that drops directly into ${var} placeholders in generatePdf templates. All accept an optional style parameter (CSS string applied to the root element). Default professional styles are used when no custom style is provided.
| Function | Returns | Description |
|---|---|---|
wf.toHtmlTable(jsonArray, columns [, style]) |
String |
JSON array → styled <table>. Auto-generates headers from key names. |
wf.toHtmlTableWithHeaders(jsonArray, columns, headers [, style]) |
String |
Same but with custom header names. |
wf.toHtmlGroupedTable(jsonArray, groupByFields, columns [, style]) |
String |
Grouped table with sub-header rows and auto subtotals. Multi-level grouping supported. |
wf.toHtmlList(jsonArray, field) |
String |
Extracts one field → <ul><li> list. |
wf.toHtmlOrderedList(jsonArray, field) |
String |
Same but as <ol> (numbered). |
wf.toHtmlKeyValue(jsonObject) |
String |
Flat JSON → key-value pairs. |
wf.toHtmlBadge(text, color) |
String |
Colored badge/tag. |
wf.toHtmlSignatureLine(name, title) |
String |
Signature block: name, title, line for signing, date. |
wf.toHtmlQrCode(data, size) |
String |
Base64-encoded QR code <img> tag. |
wf.toHtmlBarcode(data, type) |
String |
Base64-encoded barcode <img> tag. Supports CODE128 and EAN13. |
wf.* Examples
Example 1: Resolve names and format dates
<scriptTask id="prepareLabels" name="Prepare Labels" scriptFormat="super-js">
<script>
var email = execution.getVariable('assignedUser');
execution.setVariable('assignedUserName', wf.getUserFullName(email));
execution.setVariable('formattedDate', wf.formatDate(wf.now(), 'dd MMMM yyyy'));
execution.setVariable('dueDate', wf.formatDate(wf.addBusinessDays(wf.now(), 5), 'dd/MM/yyyy'));
</script>
</scriptTask>
Example 2: Build a professional invoice with helpers + generatePdf
<!-- Step 1: Prepare invoice data -->
<scriptTask id="prepInvoice" name="Prepare Invoice" scriptFormat="super-js">
<script>
var items = execution.getVariable('lineItems');
var total = wf.sum(items, 'amount');
execution.setVariable('invoiceTable', wf.toHtmlTable(items, 'name,quantity,amount'));
execution.setVariable('totalFormatted', wf.formatCurrency(total, '₺'));
execution.setVariable('totalInWords', wf.numberToWordsTR(Math.floor(total)).toString() + ' TL');
var ref = 'INV-' + wf.getYear(wf.now()) + '-' + wf.padLeft(
execution.getProcessInstanceId().substring(0, 4).toUpperCase(), 4, '0');
execution.setVariable('invoiceNumber', ref);
execution.setVariable('signatureBlock',
wf.toHtmlSignatureLine(wf.getUserFullName(execution.getVariable('initiator')), 'Requester'));
</script>
</scriptTask>
<!-- Step 2: Generate the PDF -->
<serviceTask id="genPdf" flowable:delegateExpression="${generatePdf}">
<extensionElements>
<flowable:field name="outputVariable" stringValue="invoicePdf"/>
<flowable:field name="outputFileName" stringValue="invoice"/>
<flowable:field name="template">
<flowable:string><![CDATA[
<html><body style="font-family:Arial,sans-serif;">
<h1>Invoice ${invoiceNumber}</h1>
<p>Date: ${formattedDate}</p>
${invoiceTable}
<p><strong>Total: ${totalFormatted}</strong></p>
<p>Amount in words: ${totalInWords}</p>
${signatureBlock}
</body></html>
]]></flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
Example 3: Process audit trail in a compliance PDF
<scriptTask id="buildAudit" name="Build Audit Trail" scriptFormat="super-js">
<script>
var trail = wf.getProcessAuditTrail(execution);
var jsonStr = JSON.stringify(trail);
execution.setVariable('auditTable',
wf.toHtmlTableWithHeaders(jsonStr, 'taskName,completedBy,completedDate',
'Step,Completed By,Date'));
execution.setVariable('processName', wf.getProcessName(execution));
</script>
</scriptTask>
Example 4: Document Reference Numbers
// Convention: PREFIX + YEAR + short process ID
var ref = 'INV-' + wf.getYear(wf.now()) + '-' + wf.padLeft(
execution.getProcessInstanceId().substring(0, 4).toUpperCase(), 4, '0');
// → "INV-2026-70BE"
execution.setVariable('invoiceNumber', ref);
Tip: This covers 90% of use cases. If legally-compliant sequential numbering is ever needed (
INV-2026-0001,0002, ...), that requires a dedicated DB sequence table — a future feature, not awf.*helper.
Null-safe: All
wf.*functions handle null/empty inputs gracefully and never throw exceptions.
Basic Syntax
<scriptTask id="myScript" name="Calculate Values" scriptFormat="super-js">
<script>
// Read variables
var inputValue = execution.getVariable('inputField');
// Perform calculations
var result = inputValue * 2;
// Store result for next task
execution.setVariable('calculatedResult', result);
</script>
</scriptTask>
⚠️ Critical: Type Safety & Casting
When setting variables that will be used in User Task Forms, you must ensure the type matches the form property type.
- String Form Properties (
type="string"): If you calculate a number (e.g.,var sum = 10 + 20), you MUST convert it to a string before storing it if the target form property is a string.// WRONG: Sets an Integer, causing ClassCastException in User Task execution.setVariable('average', 100); // CORRECT: Converts to String execution.setVariable('average', (100).toString()); - Long/Double Form Properties: You can store numbers directly.
- Boolean: Store strict boolean values (
true/false).
Example 1: Calculate Total from Multiple Numbers
User enters 3 numbers, Script Task calculates the sum:
<!-- User enters numbers -->
<userTask id="enterNumbers" name="Enter Numbers" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="num1" name="First Number" type="long" required="true"/>
<activiti:formProperty id="num2" name="Second Number" type="long" required="true"/>
<activiti:formProperty id="num3" name="Third Number" type="long" required="true"/>
</extensionElements>
</userTask>
<sequenceFlow sourceRef="enterNumbers" targetRef="calculateTotal"/>
<!-- Calculate total -->
<scriptTask id="calculateTotal" name="Calculate Total" scriptFormat="super-js">
<script>
var n1 = execution.getVariable('num1');
var n2 = execution.getVariable('num2');
var n3 = execution.getVariable('num3');
var total = n1 + n2 + n3;
var average = total / 3;
execution.setVariable('total', total);
// Convert to String because the form property 'average' is type="string"
var avgStr = (Math.round(average * 100) / 100).toString();
execution.setVariable('average', avgStr);
</script>
</scriptTask>
<sequenceFlow sourceRef="calculateTotal" targetRef="showResults"/>
<!-- Results are available as read-only fields -->
<userTask id="showResults" name="View Results" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="total" name="Total" type="long" writable="false"/>
<!-- Note type="string" here requires the .toString() conversion above -->
<activiti:formProperty id="average" name="Average" type="string" writable="false"/>
</extensionElements>
</userTask>
Example 2: Calculate Days Passed from a Date
User enters a date, Script Task calculates how many days have passed:
<!-- User enters a date -->
<userTask id="enterDate" name="Enter Date" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="targetDate" name="Select a Date" type="date" required="true"/>
</extensionElements>
</userTask>
<sequenceFlow sourceRef="enterDate" targetRef="calculateDays"/>
<!-- Calculate days passed -->
<scriptTask id="calculateDays" name="Calculate Days" scriptFormat="super-js">
<script>
var targetDate = execution.getVariable('targetDate');
var today = new Date();
// targetDate is a java.util.Date, get millis directly
var targetMillis = targetDate.getTime();
var todayMillis = today.getTime();
var diffMillis = todayMillis - targetMillis;
var daysPassed = Math.floor(diffMillis / (1000 * 60 * 60 * 24));
execution.setVariable('daysPassed', daysPassed);
execution.setVariable('isInPast', daysPassed > 0);
execution.setVariable('isInFuture', daysPassed < 0);
</script>
</scriptTask>
Example 3: String Operations and Conditional Logic
<scriptTask id="processText" name="Process Text" scriptFormat="super-js">
<script>
var description = execution.getVariable('description') || '';
// Check for keywords
var isUrgent = description.toLowerCase().includes('urgent');
var isPriority = description.toLowerCase().includes('priority');
// Count words
var wordCount = description.trim().split(/\s+/).length;
// Set priority level
var priorityLevel = 'normal';
if (isUrgent) priorityLevel = 'high';
if (isPriority && isUrgent) priorityLevel = 'critical';
execution.setVariable('isUrgent', isUrgent);
execution.setVariable('wordCount', wordCount);
execution.setVariable('priorityLevel', priorityLevel);
</script>
</scriptTask>
Example 4: Calculate Age from Birth Year
<scriptTask id="calculateAge" name="Calculate Age" scriptFormat="super-js">
<script>
var birthYear = execution.getVariable('birthYear');
var currentYear = new Date().getFullYear();
var age = currentYear - birthYear;
execution.setVariable('age', age);
execution.setVariable('isAdult', age >= 18);
execution.setVariable('ageGroup', age < 18 ? 'Minor' : (age < 65 ? 'Adult' : 'Senior'));
</script>
</scriptTask>
Script Task vs Service Task vs JUEL
| Feature | Script Task | Service Task | JUEL Expression |
|---|---|---|---|
| Code location | In BPMN XML | Java class | In attributes/conditions |
| Who can write | Workflow designer | Java developer | Workflow designer |
| Complexity | Medium logic | Complex logic | Simple expressions |
| Loops/conditionals | ✅ Yes | ✅ Yes | ❌ No |
| Date operations | ✅ Yes | ✅ Yes | Limited |
| Testable | Hard | Easy | N/A |
| Deployment | Upload BPMN | Redeploy app | Upload BPMN |
Tip: Use Script Tasks for medium-complexity logic that doesn't require external integrations. For complex business logic, database calls, or API integrations, use Service Tasks with Java delegates.