Skip to main content
Engineering8 min readAugust 30, 2025

Building Custom Approval Workflow Engines

Approval workflows are deceptively complex. Here's how to build a workflow engine that handles multi-step approvals, delegation, escalation, and the edge cases real organizations create.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

Why Hardcoded Approval Logic Falls Apart

Every enterprise application eventually needs approval workflows. Purchase orders above a threshold need manager approval. Time-off requests need supervisor sign-off. Contract changes need legal review. Expense reports need multi-level approval based on the amount.

The first implementation is usually hardcoded: an if-else chain that checks the amount, looks up the requester's manager, and sends an email. This works until the CFO wants purchase orders above $50K to require VP approval in addition to the manager. Then someone asks for parallel approvals — legal and finance need to approve simultaneously, not sequentially. Then a manager goes on vacation and needs to delegate their approval authority.

At this point, the hardcoded logic is a tangled mess of conditional branches that nobody fully understands, and every change risks breaking an existing flow. This is the moment to build a proper workflow engine — a configurable system that defines approval rules as data rather than code.


The Workflow Model

A workflow engine has four core concepts: workflow definitions, workflow instances, steps, and transitions.

Workflow definitions describe the abstract flow. A purchase order approval workflow might have three steps: manager approval, VP approval (conditional on amount), and finance approval. Each step specifies who can approve (a role, a specific user, or a dynamic resolver like "the requester's manager"), what actions are available (approve, reject, request changes), and what conditions trigger the step.

Workflow instances are concrete executions of a workflow definition. When a purchase order is submitted, a workflow instance is created from the PO approval definition, linked to the specific purchase order, and started. The instance tracks the current step, the history of actions taken, and the overall status.

Steps within an instance have their own lifecycle: pending (not yet reached), active (waiting for action), completed (action taken), and skipped (condition not met, step was bypassed). Each step records who it was assigned to, when it became active, who took action, what action they took, and any comments they provided.

Transitions define how the workflow moves from step to step based on the action taken. An approval might advance to the next step. A rejection might return to the requester. A "request changes" action might loop back to a previous step. Transitions can be conditional: if the VP approves but adds a note flagging risk, the workflow might add an additional review step that wouldn't otherwise exist.

Store workflow definitions as structured data — JSON or in dedicated database tables. This makes workflows configurable through an admin UI rather than code deployments. A workflow definition might look like a directed graph of steps with edges labeled by actions and conditions. The engine interprets this graph at runtime.


Assignment, Delegation, and Escalation

Who receives an approval request is straightforward in simple cases and surprisingly complex in real organizations.

Role-based assignment is the simplest: any user with the "Finance Approver" role can approve finance-related steps. This works for shared responsibilities where any team member can handle the approval.

Hierarchical assignment routes approvals based on organizational structure: send to the requester's direct manager, then to the manager's manager if the amount exceeds a threshold. This requires that your system has an accurate org chart, which is often harder to maintain than it sounds.

Dynamic resolution handles cases where the approver depends on the specific request. A purchase order for the marketing department goes to the marketing budget owner. A contract change goes to the account's assigned legal counsel. The resolver is a function that takes the workflow context and returns the appropriate approver.

Delegation handles the reality that people go on vacation, change roles, or are simply unavailable. A user should be able to delegate their approval authority to another user for a specified time period. The workflow engine checks for active delegations when assigning steps and routes accordingly. Delegated approvals should be clearly marked in the audit trail — the system records both the delegator and the person who actually took the action.

Escalation handles the reality that people don't always respond promptly. After a configurable period (24 hours, 3 business days), an unactioned approval step should escalate — notify the approver's manager, reassign to a backup approver, or simply send a reminder. Escalation rules should be configurable per workflow and per step, because a routine expense approval can wait 3 days but a time-sensitive contract approval might need to escalate after 4 hours.


Parallel Approvals and Complex Flows

Simple sequential workflows — step 1 then step 2 then step 3 — handle many cases, but enterprise organizations regularly need more complex patterns.

Parallel approvals require multiple approvers to act independently and simultaneously. A large contract might need legal, finance, and executive approval in parallel. The workflow advances when all parallel branches complete (AND logic) or when any one completes (OR logic). AND-join is more common for approvals: you need everyone's sign-off.

The implementation for parallel steps uses a fork-join pattern. When the workflow reaches a parallel gateway, it creates multiple active steps simultaneously. Each step is processed independently. When the last parallel step completes, the join gateway activates and the workflow advances to the next sequential step. Track the completion count against the expected count to detect when the join condition is met.

Conditional branching routes the workflow differently based on data. Purchase orders under $10K skip VP approval. Requests from certain departments add a compliance review step. These conditions are evaluated at runtime using the workflow context — the entity being approved, the requester's attributes, and any data collected during previous steps.

Loops handle revision cycles. When an approver requests changes, the workflow returns to the requester, who makes modifications and resubmits. The workflow then re-enters the approval steps. Without a loop limit, this can cycle indefinitely, so set a maximum iteration count and escalate if the loop exceeds it.

These patterns are closely related to the concepts in event-driven architecture, where the workflow engine acts as an orchestrator coordinating actions across the system based on events and conditions.


Integration With the Rest of the Application

The workflow engine needs clean integration points with the host application.

Triggering workflows should be event-driven. When a purchase order is created and submitted, an event triggers the creation of a workflow instance. The PO module doesn't need to know the details of the approval workflow — it publishes an event, and the workflow engine subscribes and acts.

Action callbacks notify the application when workflow events occur. When an approval is granted, the application needs to know so it can update the purchase order status to "approved" and trigger downstream processes like sending the PO to the vendor. These callbacks should be reliable — if the callback fails, the workflow engine should retry rather than leaving the workflow and the application in inconsistent states.

Status queries let the application display workflow status in its UI. The purchase order detail page should show the current approval status, who has approved, who is pending, and the estimated completion time. Expose this through an API that the frontend can query without coupling the UI to the workflow engine's internals.

The audit trail for workflow actions is also critical — every approval, rejection, delegation, and escalation is an auditable event that should feed into your enterprise audit system.

If you're building approval workflows for your enterprise application, let's discuss the architecture.


Keep Reading