Skip to main content

Workflows

Workflows automate business processes by defining states, transitions, and actions that execute based on conditions.


When to Use Workflows

Use CaseExample
Approval processesInvoice approval routing
Status managementOrder lifecycle tracking
NotificationsAlert on record changes
Field updatesAuto-populate based on rules
ValidationEnforce business rules

Workflow Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│ WORKFLOW STRUCTURE │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ WORKFLOW DEFINITION │
│ custworkflow_invoice_approval │
│ ────────────────────────────────────────────────────────────────│
│ Record Type: Vendor Bill │
│ Trigger: On Create, On Update │
└──────────────────────────────────────────────────────────────────┘

┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ STATE 1 │ │ STATE 2 │ │ STATE 3 │
│ Draft/New │ │ Pending │ │ Approved │
│ │ │ Approval │ │ │
│ Entry Actions: │ │ Entry Actions: │ │ Entry Actions: │
│ • Set status │ │ • Send email │ │ • Update status │
│ │ │ • Lock record │ │ • Unlock record │
│ Exit Actions: │ │ Exit Actions: │ │ │
│ • Validate │ │ • Log action │ │ (Final State) │
└────────┬────────┘ └────────┬────────┘ └─────────────────┘
│ │
│ Transition: │ Transition:
│ "Submit for │ "Approve"
│ Approval" │
│ ───────────── │ ─────────────
│ Condition: │ Condition:
│ amount > 1000 │ Button click
│ │
└─────────────────────┘

Workflow Execution Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│ WORKFLOW EXECUTION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ TRIGGER EVENT │
│ ────────────────────────────────────────────────────────────────│
│ • Record Created │
│ • Record Updated │
│ • Record Approved │
│ • Scheduled (time-based) │
└──────────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────┐
│ CHECK WORKFLOW CONDITIONS │
│ ────────────────────────────────────────────────────────────────│
│ Does record match workflow initiation conditions? │
│ Example: Type = Vendor Bill AND Amount > $1,000 │
└──────────────────────────────┬───────────────────────────────────┘

┌────────────No───────┴───────Yes────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────────────────────┐
│ No Action │ │ ENTER INITIAL STATE │
│ (Skip) │ │ Execute Entry Actions │
└─────────────────┘ └──────────────┬───────────────────┘


┌──────────────────────────────────┐
│ EVALUATE TRANSITIONS │
│ ────────────────────────────────│
│ Check transition conditions │
│ • Button clicked? │
│ • Condition met? │
│ • Scheduled trigger? │
└──────────────┬───────────────────┘

┌───────Condition Met───┴───No Match──────┐
│ │
▼ ▼
┌────────────────────────────────┐ ┌────────────────────┐
│ EXECUTE TRANSITION │ │ WAIT │
│ ──────────────────────────── │ │ (Stay in state │
│ 1. Run exit actions │ │ until condition │
│ 2. Run transition actions │ │ met) │
│ 3. Move to next state │ └────────────────────┘
│ 4. Run entry actions │
└────────────────────────────────┘

Basic Workflow XML

<?xml version="1.0" encoding="UTF-8"?>
<workflow scriptid="custworkflow_invoice_approval">
<name>Invoice Approval Workflow</name>
<description>Routes vendor bills for approval based on amount</description>

<!-- Record Type -->
<recordtype>vendorbill</recordtype>

<!-- Release Status -->
<releasestatus>RELEASED</releasestatus>

<!-- Initiation -->
<initoncreate>T</initoncreate>
<initonupdate>F</initonupdate>
<initonvieworupdate>F</initonvieworupdate>

<!-- Initiation Condition -->
<initcondition>
<formula>{total} > 1000</formula>
</initcondition>

<!-- Keep instance active -->
<keepinstancealivetillcomplete>T</keepinstancealivetillcomplete>

<!-- Workflow States -->
<workflowstates>

<!-- State 1: Pending Approval -->
<workflowstate scriptid="workflowstate_pending">
<name>Pending Approval</name>
<description>Waiting for manager approval</description>
<donotexitalivetillcomplete>T</donotexitalivetillcomplete>

<!-- Entry Actions -->
<workflowactions>
<triggertype>ENTRY</triggertype>

<!-- Set Field Value -->
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Approval</value>
</setfieldvalueaction>

<!-- Send Email -->
<sendemailaction>
<recipientfield>custbody_approver</recipientfield>
<recipientisemail>F</recipientisemail>
<senderfield>currentuser</senderfield>
<subject>Invoice Approval Required: {tranid}</subject>
<body>Please review and approve invoice {tranid} for ${total}</body>
</sendemailaction>

</workflowactions>

<!-- Transitions from this state -->
<workflowtransitions>

<!-- Transition: Approve -->
<workflowtransition scriptid="transition_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<tostate>workflowstate_approved</tostate>
<buttonaction>
<button>Approve</button>
</buttonaction>
</workflowtransition>

<!-- Transition: Reject -->
<workflowtransition scriptid="transition_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<tostate>workflowstate_rejected</tostate>
<buttonaction>
<button>Reject</button>
</buttonaction>
</workflowtransition>

</workflowtransitions>

</workflowstate>

<!-- State 2: Approved -->
<workflowstate scriptid="workflowstate_approved">
<name>Approved</name>
<description>Invoice has been approved</description>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Approved</value>
</setfieldvalueaction>

<setfieldvalueaction>
<field>custbody_approved_date</field>
<valuetype>STATIC</valuetype>
<value>NOW</value>
</setfieldvalueaction>

</workflowactions>

</workflowstate>

<!-- State 3: Rejected -->
<workflowstate scriptid="workflowstate_rejected">
<name>Rejected</name>
<description>Invoice has been rejected</description>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Rejected</value>
</setfieldvalueaction>

<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>Invoice Rejected: {tranid}</subject>
<body>Your invoice {tranid} has been rejected.</body>
</sendemailaction>

</workflowactions>

</workflowstate>

</workflowstates>

</workflow>

Workflow Actions

Set Field Value

<setfieldvalueaction>
<field>custbody_status</field>
<valuetype>STATIC</valuetype>
<value>Pending</value>
</setfieldvalueaction>

<!-- Dynamic value from another field -->
<setfieldvalueaction>
<field>custbody_approved_by</field>
<valuetype>FIELD</valuetype>
<valuefield>currentuser</valuefield>
</setfieldvalueaction>

<!-- Formula value -->
<setfieldvalueaction>
<field>custbody_due_date</field>
<valuetype>FORMULA</valuetype>
<value>{today}+30</value>
</setfieldvalueaction>

Send Email

<sendemailaction>
<recipientfield>custbody_approver</recipientfield>
<recipientisemail>F</recipientisemail>
<senderfield>currentuser</senderfield>
<usetemplate>T</usetemplate>
<template>Email Template Name</template>
</sendemailaction>

Create Record

<createrecordaction>
<recordtype>customrecord_approval_log</recordtype>
<createrecordfield>
<field>custrecord_al_record</field>
<valuefield>id</valuefield>
</createrecordfield>
<createrecordfield>
<field>custrecord_al_action</field>
<value>Submitted for Approval</value>
</createrecordfield>
<createrecordfield>
<field>custrecord_al_user</field>
<valuefield>currentuser</valuefield>
</createrecordfield>
</createrecordaction>

Custom Action (Script)

<customaction>
<scriptid>customscript_approval_action</scriptid>
<scriptdeploymentid>customdeploy_approval_action</scriptdeploymentid>
<parameters>
<parameter>
<name>custscript_action_type</name>
<value>approve</value>
</parameter>
</parameters>
</customaction>

Lock Record

<lockrecordaction>
<locked>T</locked>
</lockrecordaction>

Transition Types

┌─────────────────────────────────────────────────────────────────────────────┐
│ TRANSITION TRIGGER TYPES │
└─────────────────────────────────────────────────────────────────────────────┘

┌────────────────────┬─────────────────────────────────────────────────────────┐
│ BUTTON TRIGGER │ User clicks workflow button │
│ │ <buttonaction> │
│ │ <button>Approve</button> │
│ │ </buttonaction> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ CONDITION │ Field value meets condition │
│ TRIGGER │ <condition> │
│ │ <formula>{total} > 10000</formula> │
│ │ </condition> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ SCHEDULED │ Time-based trigger │
│ TRIGGER │ <scheduledaction> │
│ │ <delay>1</delay> │
│ │ <delayunit>HOURS</delayunit> │
│ │ </scheduledaction> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ EVENT │ Record event (save, submit, etc.) │
│ TRIGGER │ <triggerevent>AFTERSUBMIT</triggerevent> │
└────────────────────┴─────────────────────────────────────────────────────────┘

Trigger Events

EventWhen It Fires
BEFORELOADBefore record loads
BEFORESUBMITBefore record saves
AFTERSUBMITAfter record saves
ONENTRYWhen entering state
ONEXITWhen exiting state
SCHEDULEDAt scheduled time

Workflow Conditions

Field-Based Conditions

<condition>
<formula>{total} > 5000</formula>
</condition>

<condition>
<formula>{custbody_region} = 'West'</formula>
</condition>

<condition>
<formula>{entity.custentity_credit_rating} = 'A'</formula>
</condition>

Combined Conditions

<condition>
<formula>{total} > 5000 AND {custbody_requires_approval} = 'T'</formula>
</condition>

<condition>
<formula>{total} > 10000 OR {custbody_rush_order} = 'T'</formula>
</condition>

Saved Search Conditions

<condition>
<savedsearch>customsearch_high_risk_customers</savedsearch>
</condition>

Complete Example: Multi-Level Approval

<?xml version="1.0" encoding="UTF-8"?>
<workflow scriptid="custworkflow_po_approval">
<name>Purchase Order Approval</name>
<description>Multi-level approval based on PO amount</description>
<recordtype>purchaseorder</recordtype>
<releasestatus>RELEASED</releasestatus>

<initoncreate>T</initoncreate>
<initonupdate>T</initonupdate>

<initcondition>
<formula>{total} > 500</formula>
</initcondition>

<workflowstates>

<!-- State: Draft -->
<workflowstate scriptid="state_draft">
<name>Draft</name>
<donotexitalivetillcomplete>T</donotexitalivetillcomplete>

<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Submission</value>
</setfieldvalueaction>
</workflowactions>

<workflowtransitions>
<workflowtransition scriptid="trans_submit">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Submit for Approval</button>
</buttonaction>

<!-- Route based on amount -->
<tostate>state_manager_review</tostate>
<condition>
<formula>{total} <= 5000</formula>
</condition>
</workflowtransition>

<workflowtransition scriptid="trans_submit_high">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Submit for Approval</button>
</buttonaction>
<tostate>state_director_review</tostate>
<condition>
<formula>{total} > 5000</formula>
</condition>
</workflowtransition>
</workflowtransitions>
</workflowstate>

<!-- State: Manager Review -->
<workflowstate scriptid="state_manager_review">
<name>Manager Review</name>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Approval</value>
</setfieldvalueaction>

<lockrecordaction>
<locked>T</locked>
</lockrecordaction>

<sendemailaction>
<recipientfield>custbody_manager</recipientfield>
<subject>PO Approval Required: {tranid}</subject>
<body>Please review PO {tranid} for ${total}</body>
</sendemailaction>
</workflowactions>

<workflowtransitions>
<workflowtransition scriptid="trans_mgr_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>

<workflowtransition scriptid="trans_mgr_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>

<!-- State: Director Review (for high value) -->
<workflowstate scriptid="state_director_review">
<name>Director Review</name>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Director Approval</value>
</setfieldvalueaction>

<lockrecordaction>
<locked>T</locked>
</lockrecordaction>

<sendemailaction>
<recipientfield>custbody_director</recipientfield>
<subject>High Value PO Approval: {tranid}</subject>
<body>PO {tranid} for ${total} requires your approval.</body>
</sendemailaction>
</workflowactions>

<workflowtransitions>
<workflowtransition scriptid="trans_dir_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>

<workflowtransition scriptid="trans_dir_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>

<workflowtransition scriptid="trans_dir_escalate">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Escalate to CFO</button>
</buttonaction>
<tostate>state_cfo_review</tostate>
<condition>
<formula>{total} > 50000</formula>
</condition>
</workflowtransition>
</workflowtransitions>
</workflowstate>

<!-- State: CFO Review -->
<workflowstate scriptid="state_cfo_review">
<name>CFO Review</name>

<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending CFO Approval</value>
</setfieldvalueaction>
</workflowactions>

<workflowtransitions>
<workflowtransition scriptid="trans_cfo_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>

<workflowtransition scriptid="trans_cfo_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>

<!-- State: Approved -->
<workflowstate scriptid="state_approved">
<name>Approved</name>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Approved</value>
</setfieldvalueaction>

<setfieldvalueaction>
<field>custbody_approved_by</field>
<valuetype>FIELD</valuetype>
<valuefield>currentuser</valuefield>
</setfieldvalueaction>

<setfieldvalueaction>
<field>custbody_approved_date</field>
<valuetype>STATIC</valuetype>
<value>NOW</value>
</setfieldvalueaction>

<lockrecordaction>
<locked>F</locked>
</lockrecordaction>

<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>PO Approved: {tranid}</subject>
<body>Your PO {tranid} has been approved.</body>
</sendemailaction>
</workflowactions>
</workflowstate>

<!-- State: Rejected -->
<workflowstate scriptid="state_rejected">
<name>Rejected</name>

<workflowactions>
<triggertype>ENTRY</triggertype>

<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Rejected</value>
</setfieldvalueaction>

<lockrecordaction>
<locked>F</locked>
</lockrecordaction>

<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>PO Rejected: {tranid}</subject>
<body>Your PO {tranid} has been rejected.</body>
</sendemailaction>
</workflowactions>

<workflowtransitions>
<!-- Allow resubmission -->
<workflowtransition scriptid="trans_resubmit">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Resubmit</button>
</buttonaction>
<tostate>state_draft</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>

</workflowstates>

</workflow>

Workflow State Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│ MULTI-LEVEL APPROVAL FLOW │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐
│ DRAFT │
│ (Initial) │
└──────┬──────┘

Submit for Approval

┌──────────────────────┴───────────────────────┐
│ │
Amount <= $5,000 Amount > $5,000
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ MANAGER │ │ DIRECTOR │
│ REVIEW │ │ REVIEW │
└────────┬────────┘ └────────┬────────┘
│ │
┌─────────┴─────────┐ ┌─────────────────┼─────────────────┐
│ │ │ │ │
Approve Reject Approve Reject Escalate
│ │ │ │ (> $50,000)
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ ┌──────────┐
│APPROVED│ │ REJECTED │ │APPROVED│ │ REJECTED │ │ CFO │
└────────┘ └─────┬─────┘ └────────┘ └─────┬─────┘ │ REVIEW │
│ │ └─────┬────┘
│ │ │
Resubmit Resubmit Approve/Reject
│ │ │
└─────────────►DRAFT◄─────────────┘ ▼
┌────────────────┐
│ APPROVED / │
│ REJECTED │
└────────────────┘

Import Existing Workflows

# Import all workflows
suitecloud object:import --type workflow --destinationfolder Objects

# Import specific workflow
suitecloud object:import --type workflow --scriptid custworkflow_invoice_approval --destinationfolder Objects

Best Practices

PracticeDescription
Clear namingUse descriptive state and transition names
Document flowsInclude descriptions for each state
Handle all pathsAccount for approve, reject, and edge cases
Use conditionsRoute based on business rules
Test thoroughlyTest all possible paths
Log actionsCreate audit trail records

Next Steps