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 standard javascript or js formats may not have access to the execution object 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 execution object as their first parameter. This is the same execution object designers already use for execution.getVariable(). If omitted, these methods return empty/null (null-safe). All other functions do NOT require execution.

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() or wf.addBusinessDays() directly inside wf.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 a wf.* 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.