Workflow Editor

The Workflow Editor is how admins build automated multi-step processes: enrollment flows, email sequences, decision branches. It is a visual editor with a drag-and-drop canvas. Users pick node types (Form, Email, Decision, Wait) from a left palette, drop them onto the canvas, connect them to define the order, and configure each step in a right-side panel. Built on React Flow for the canvas and node graph, with MUI components for all the chrome. This is a unique full-screen layout. It does not use standard content padding.


Overview

The Workflow Editor lets admins build automated multi-step processes by arranging nodes on a canvas. Each node type (Form, Email, Decision, Wait, Markdown) has its own configuration. Nodes connect via drag-and-drop edges.

When to use

Admin needs to define a multi-step automated process with branching logic

Key interaction

Drag nodes from palette, connect on canvas, configure in side panel

Library

React Flow for canvas, MUI for chrome and controls

Interactive Preview

Workflow Editor

Click to add to canvas

Form

Collect user input through a configured form template.

Email

Send an automated email using a saved template.

Decision

Branch the workflow based on a true/false condition.

Manual Decision

Pause the workflow and wait for an admin to approve or decline before continuing.

100%
YesNoApproved

Form

Student Application

Decision

Eligible?

Manual Decision

Application Review

Email

Decision Explainer

Email

Welcome Email

Form

Course Selection

Node Settings
Type
Decision
Name

Condition expression
Yes branch label
No branch label
Node IDn2

Node Types

Each node type represents a step in a workflow. Below is the full catalog: every node's canvas appearance, default settings, and the configurable fields shown in the properties panel when selected.

Form

Collect user input through a configured form template.

Canvas preview

Form

New Form

Settings (2)

Form template

template
select

Required

required
boolean
Defaults
template: "Application"
required: true

Email

Send an automated email using a saved template.

Canvas preview

Email

New Email

Settings (2)

Template

template
select

Recipient

recipient
text
Defaults
template: "Welcome"
recipient: "{{user.email}}"

Decision

Branch the workflow based on a true/false condition.

Canvas preview

Decision

New Decision

Settings (3)

Condition expression

condition
text

Yes branch label

yesLabel
text

No branch label

noLabel
text
Defaults
condition: "user.age >= 18"
yesLabel: "Yes"
noLabel: "No"

Manual Decision

Pause the workflow and wait for an admin to approve or decline before continuing.

Canvas preview

Manual Decision

Application Review

Settings (3)

Decision name

decisionName
text

Context variables

contextVariables
multiselect

End-user message

endUserMessage
textarea
Defaults
decisionName: ""
contextVariables: user.full_name,user.email,application.id
endUserMessage: "Your application is being reviewed. We will notify you once a decision has been made."

Wait

Pause the workflow for a fixed duration before continuing.

Canvas preview

Wait

Wait

Settings (2)

Duration

duration
number

Unit

unit
select
Defaults
duration: 1
unit: "days"

Markdown

Display formatted text or instructions inline in the workflow.

Canvas preview

Markdown

Heading

Settings (2)

Content

content
textarea

Alignment

align
select
Defaults
content: "Welcome to the workflow"
align: "left"

Palette Items

The left panel renders one draggable card per node type. Each item shows a drag handle, the type icon, the type name, and a short description.

Examples

Form

Collect user input through a configured form template.

Email

Send an automated email using a saved template.

Decision

Branch the workflow based on a true/false condition.

Property
Value

Panel width

240px

Item padding

12px

Item gap

between items8px

Border

1px soliddivider

Border radius

8pxradiusMd

Background

background.paper

Hover border

1px solidprimary

Drag handle

DragIndicatorIcon16px, gray500

Type icon

20pxcolor per node type

Title

14px600 weight, navy

Description

12pxgray500

Inner gap

drag handle to icon to text12px

Cursor

grab
·while dragginggrabbing

Drag API

HTML5 drag-and-drop, React Flow onDrop

Canvas Node States

Nodes on the canvas transition between states based on user interaction. Clicking a node selects it and opens the properties panel. Selected nodes show a CTA when they require configuration.

Default

Form

No form selected

Selected

Form

Application Form

Property
Value

Width

180px

Radius

10pxradiusLg

Shadow

card token

Icon

16pxnode-type color

Title

14px600 weight, navy

Subtitle

12pxgray500

Handle size

10px circlecentered on edge

Connected Nodes

Nodes connect via source (right) and target (left) handles in a horizontal flow. Edges are drawn as lines between handles. Decision nodes support multiple outputs for branching logic.

Property
Value

Flow direction

horizontal (left to right)

Edge line

2px solidgray300

Edge (selected)

2px solidprimary

Snap to grid

24px grid

Properties Panel

Appears on the right when a node is selected. Shows editable properties, read-only metadata, and an expand option for complex editors (Form Editor, Email Template Editor).

Node Properties
Name
Type
Form
IDnode_f7a2b3c1

Comment
Property
Value

Panel width

280–320px

Trigger

Appears on node select, hides on deselect

Validation States

The workflow editor validates in real-time. Invalid nodes show visual indicators. The save button is disabled until all errors are resolved. Orphaned (unreachable) nodes show warnings.

Valid

Form

Application Form

Invalid (error)

Form

Missing required fields

2px danger border + error icon
Orphaned (unreachable)

Send Reminder

No incoming connections

Dashed warning border + warning icon
Property
Value

Invalid node border

2px solid #EF4444danger

Error indicator

ErrorIcontop-right of node, danger color

Invalid connection

red flash on handle + prevented

Header Kebab Menu

The 3-dot menu in the header strip contains secondary and destructive actions. This keeps the bottom bar focused on the primary save/cancel flow and prevents accidental destructive clicks.

Menu Contents
Duplicate workflow
Export as JSON

Delete workflow
Design Rationale

Separation of concerns

Destructive actions (delete) are behind an extra click, away from save/cancel.

Follows existing pattern

Matches the 3-dot context menu used on list cards throughout the app.

Extensible

Secondary actions like duplicate and export can be added here without cluttering the header.

Test Mode

Admins can switch from Edit to Test mode to simulate workflow execution. Nodes are highlighted step-by-step as the test runs. Variable values can be simulated, and branch paths can be manually selected at Decision nodes.

Test Mode Active

Form

Done

Email

Running

Decision

Property
Value

Mode toggle

Edit ↔ Testsegmented control in header

Active indicator

amber banner below header when in test mode

Variable Management

Workflows maintain a set of variables. Some are global to the workflow, others are generated by individual nodes. Each node accesses data from previous nodes in the execution chain. Variables use a reference syntax for dynamic values in node configurations.

Variable Reference in Config
Email Recipient
Subject Line

Variables use {{node_name.field}} syntax

Variable Scoping

Global

Process-level variables available to all nodes (e.g. process ID, admin user)

Node output

Data generated by a node, available to all subsequent nodes in the chain

Context

Runtime data from the parent process (e.g. student record, cohort info)

Interaction Flow

1

Entry

User opens workflow editor from a process. Breadcrumb shows full path. Node palette is visible on the left.

2

Drag to add

User drags a node type from the palette onto the canvas. A new node appears with connection handles.

3

Select node

Clicking a node highlights it with 2px orange border + primaryLight background. Properties panel appears on the right.

4

Configure

User edits node settings in the properties panel. Form nodes can open the Form Editor. Changes reflect live.

5

Connect nodes

User drags from a source handle to a target handle to create edges. Decision nodes create branching paths.

6

Save / Delete

Bottom bar: "Save" persists the workflow, "Cancel" discards changes. "Delete workflow" is in the header kebab menu (3-dot) to separate destructive from constructive actions.

Component Map

Property
Value

Breadcrumb bar

Breadcrumbs52px topbar

Header strip

Boxtitle input + description + status select

Header kebab menu

Menu + MenuItemDelete workflow (danger item)

Bottom actions

Buttonoutlined Cancel + contained Save

Constraints & Notes

Known limitations and platform requirements for v1 of the Workflow Editor.

Property
Value

Platform

Desktop only, no mobile support

Access

Admins and super-admins only

Performance

Canvas slows with 200+ nodes

Versioning

Not supported in v1, no history or rollback

Concurrent editing

Not supported, one editor at a time

Data format

JSON serialization for workflow graphs

Library

React Flowprimary canvas library

Code

React Flow custom node
function FormNode({ data, selected }) {
  return (
    <Box
      sx={{
        border: selected ? '2px solid' : '1px solid',
        borderColor: selected ? 'primary.main' : 'grey.200',
        backgroundColor: selected ? 'primary.light' : 'white',
        borderRadius: '10px',
        p: 2,
        width: 180,
      }}
    >
      <Handle type="target" position={Position.Top} />
      <Stack direction="row" alignItems="center" gap={1}>
        <DescriptionIcon sx={{ fontSize: 16, color: 'primary.main' }} />
        <Typography variant="body2" fontWeight={600}>Form</Typography>
      </Stack>
      <Typography variant="caption" color="text.secondary">
        {data.label || 'No form selected'}
      </Typography>
      <Handle type="source" position={Position.Bottom} />
    </Box>
  );
}
Node palette item
<Box
  draggable
  onDragStart={(e) => {
    e.dataTransfer.setData('application/reactflow', nodeType);
    e.dataTransfer.effectAllowed = 'move';
  }}
  sx={{
    display: 'flex', alignItems: 'center', gap: 1.5,
    p: 1.5, border: '1px solid', borderColor: 'divider',
    borderRadius: '8px', cursor: 'grab',
    '&:hover': { borderColor: 'primary.main' },
  }}
>
  <DragIndicatorIcon sx={{ fontSize: 16, color: 'grey.500' }} />
  <DescriptionIcon sx={{ fontSize: 20, color: 'primary.main' }} />
  <Box>
    <Typography variant="body2" fontWeight={600}>Form</Typography>
    <Typography variant="caption" color="text.secondary">
      Collect user input and data
    </Typography>
  </Box>
</Box>
Custom handle (single, styled with tokens)
import { Handle, Position } from '@xyflow/react';
import { tokens } from '@/theme/theme';

// Place inside a React Flow node component:
<Handle
  type="target"
  position={Position.Left}
  style={{
    background: '#FFFFFF',
    width: 11,
    height: 11,
    border: `1px solid ${tokens.color.gray400}`,
    boxShadow: '0 1px 2px rgba(0,0,0,0.12)',
  }}
/>
Multiple source handles (Decision branches)
// Decision nodes have two outputs: yes and no.
// Each handle needs a unique id so edges can target it.

<Handle
  type="source"
  id="yes"
  position={Position.Right}
  style={{
    top: '30%',
    background: '#FFFFFF',
    width: 11,
    height: 11,
    border: `1px solid ${tokens.color.success}`,
  }}
/>
<Handle
  type="source"
  id="no"
  position={Position.Right}
  style={{
    top: '70%',
    background: '#FFFFFF',
    width: 11,
    height: 11,
    border: `1px solid ${tokens.color.danger}`,
  }}
/>
Styled edge with branch label
// Edge object passed to React Flow:
const edges = [
  {
    id: 'e-decision-yes',
    source: 'decision',
    sourceHandle: 'yes',
    target: 'email-welcome',
    style: {
      stroke: tokens.color.gray400,
      strokeWidth: 2,
    },
    label: 'Yes',
    labelStyle: {
      fontSize: 11,
      fontWeight: 600,
      fill: tokens.color.successDark,
    },
    labelBgStyle: {
      fill: tokens.color.successLight,
    },
    labelBgPadding: [6, 4],
    labelBgBorderRadius: 9,
    markerEnd: { type: 'arrowclosed', color: tokens.color.gray400 },
  },
];