AEM Workflows: The Complete Guide

11 min read

A practical guide to AEM Workflows — workflow models, launchers, the step types (process, participant, dynamic participant, and more), and how to write a custom workflow process in Java, with payload access, routing, and transient workflows. Includes code, a cheat sheet, best practices, and do's & don'ts.

AEMWorkflowsJavaAutomationReference

Workflows are how AEM automates the processes around content: review-and-approve before publishing, the asset processing that runs on every upload, translation, scheduled activation, and any custom business logic you need to chain together. Anything that should happen "when X, then do Y, then route to Z for approval" is a workflow.

This guide explains the whole model — how a workflow model is designed, how launchers start one automatically, the step types you assemble it from (with a focus on process and participant steps), and how to write your own custom workflow process in Java. There's runnable code throughout, and a cheat sheet, best practices, and do's & don'ts at the end.

It expands on the workflow section of the Backend Development guide, relates to the Assets guide (the DAM Update Asset workflow), and contrasts with Sling Jobs from the Sling guide.

The core concepts

A few terms recur throughout, so it's worth pinning them down first:

  • A workflow model is the design — a sequence of steps connected by transitions. It's the template.
  • A workflow instance is a running execution of a model against a specific piece of content.
  • The payload is the content the workflow acts on — usually a page or asset path (a JCR_PATH).
  • A work item is the current active step of an instance; for a human step it lands in someone's Inbox.
  • A launcher is a rule that starts a workflow automatically when content changes.

In short: you design a model once, a launcher (or a user, or code) starts an instance of it against a payload, and the instance moves from step to step, occasionally pausing on a work item for a human.

Workflow Models

A workflow model is built visually in the Workflow editor (/libs/cq/workflow/content/console.html), where you drag steps onto a flow and connect them with transitions. On modern AEM, models are stored as editable definitions under /conf/global/settings/workflow/models, with a synced runtime copy under /var/workflow/models that instances actually execute.

Models can express real control flow, not just a straight line:

  • Transitions connect steps in sequence.
  • OR splits branch down one of several paths based on a condition (e.g. route by region).
  • AND splits run multiple branches in parallel and rejoin.
  • Containers embed a sub-workflow, so you can compose models from reusable pieces.
  • Goto steps jump to another step, enabling loops and conditional skips.

The two practical things to know: edit the model in /conf (and deploy it as code), and remember that there's a model vs. runtime model distinction — if a model change doesn't seem to take effect, the runtime copy may need to sync.

Launchers

Most workflows don't get started by a person clicking a button — they're triggered automatically by a launcher. A launcher watches the repository and, when content matching its conditions changes, kicks off a workflow model. This is exactly how the DAM Update Asset workflow runs on every asset upload.

You configure launchers in the Workflow console's Launchers tab (stored under /conf), and each one binds a set of conditions to a model:

Launcher fieldPurpose
Event TypeCreated, Modified, or Removed
Node TypeThe type to watch (e.g. dam:Asset, cq:PageContent)
PathA glob restricting where it applies (e.g. /content/dam/mysite(/.*)?)
ConditionA property test (e.g. jcr:content/jcr:title != null)
WorkflowThe model to start
Run ModesRestrict to author/publish
Exclude ListUsers/paths to ignore (avoids loops)

Important: Launchers are a classic source of infinite loops — if a workflow modifies the very content that launched it, it can re-trigger itself. Scope the launcher tightly (path, node type, condition) and use the exclude list / a "workflow service user" exclusion so a workflow's own writes don't relaunch it.

The step types

A model is assembled from steps, and AEM provides several built-in types. Knowing what each does tells you which to drag onto the canvas:

StepWhat it does
Process StepRuns Java (or ECMAScript) automatically — no human
Participant StepAssigns a work item to a user/group; pauses for their action
Dynamic Participant StepLike Participant, but the assignee is chosen by code
Dialog Participant StepA participant step that also collects data via a dialog
Container StepRuns another workflow model as a sub-process
OR / AND SplitConditional branch / parallel branches
Goto StepJump to another step (loops, conditional skips)
Set VariableSet workflow metadata/variables for later steps

The two you'll work with most — and write code for — are process and participant steps.

Process Steps

A process step runs automatically with no human involvement — it's where your Java logic lives. Validate content, transform an asset, call an external service, replicate a page: all process steps. You implement the WorkflowProcess interface and register it as an OSGi service with a process.label, which is the name authors pick from in the step's configuration.

@Component(
    service = WorkflowProcess.class,
    property = { "process.label=My Site - Validate Page" })
public class ValidatePageStep implements WorkflowProcess {
    @Override
    public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap args)
            throws WorkflowException {
        String path = workItem.getWorkflowData().getPayload().toString();
        // ... run validation; throw WorkflowException to fail the step ...
    }
}

Because process steps run inside the workflow engine, keep them fast and reliable — the full "custom process" treatment, including payload access, arguments, and routing, is the section below.

Participant Steps

A participant step is where a human enters the loop. When an instance reaches one, it pauses and creates a work item assigned to a user or group, which appears in their Inbox; the workflow only continues once that person acts. This is the backbone of review-and-approve flows: an author submits a page, it pauses at "Legal Review," and only an approver's decision advances (or rejects) it.

When you don't know the assignee at design time, use a Dynamic Participant Step, where a ParticipantStepChooser picks the assignee in code — for example, routing to a region-specific approver group based on the page's path:

@Component(
    service = ParticipantStepChooser.class,
    property = { "chooser.label=My Site - Region Approver" })
public class RegionApproverChooser implements ParticipantStepChooser {
    @Override
    public String getParticipant(WorkItem workItem, WorkflowSession wfSession, MetaDataMap args)
            throws WorkflowException {
        String path = workItem.getWorkflowData().getPayload().toString();
        return path.contains("/eu/") ? "eu-approvers" : "global-approvers"; // group id
    }
}

The returned string is the principal (user or group id) the work item is assigned to. A Dialog Participant Step goes further, presenting the participant a form so they can supply data the workflow then acts on.

Custom Workflow Process (in depth)

The custom process step is where most real workflow development happens, so here's the full picture. The execute method receives three objects, and together they give you everything you need:

  • WorkItem — the current step, and your handle to the payload and workflow data.
  • WorkflowSession — lets you advance, route, restart, or terminate the workflow.
  • MetaDataMap — delivers the step's configured arguments and lets you read/write workflow metadata shared across steps.

A complete step that reads its payload and arguments, does work, and routes the workflow looks like this:

@Component(
    service = WorkflowProcess.class,
    property = { "process.label=My Site - Approve & Publish" })
public class ApproveAndPublishStep implements WorkflowProcess {

    @Reference
    private Replicator replicator;

    @Override
    public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap args)
            throws WorkflowException {

        WorkflowData data = workItem.getWorkflowData();
        if (!"JCR_PATH".equals(data.getPayloadType())) return;
        String path = data.getPayload().toString();

        // Step arguments configured on the model (the PROCESS_ARGS field)
        String mode = args.get("PROCESS_ARGS", String.class);

        try {
            Session session = wfSession.adaptTo(Session.class);
            replicator.replicate(session, ReplicationActionType.ACTIVATE, path);
        } catch (ReplicationException e) {
            throw new WorkflowException("Publish failed for " + path, e);
        }

        // Optional explicit routing through an OR split:
        // List<Route> routes = wfSession.getRoutes(workItem);
        // wfSession.complete(workItem, routes.get(0));
    }
}

A few things worth calling out:

  • Read the payload via workItem.getWorkflowData().getPayload(), and check getPayloadType() is JCR_PATH before trusting it.
  • Arguments come through args.get("PROCESS_ARGS", String.class) — the value authors type into the step's "Arguments" field.
  • Routing: for a normal linear step you don't route manually; for an OR split, call wfSession.getRoutes(workItem) and wfSession.complete(workItem, route) to choose a branch.
  • Failure: throw a WorkflowException to fail the step (it can then be retried or surfaced to an admin).
  • Heavy or unreliable work (slow external calls) should be pushed to a Sling Job so the step completes quickly and the engine isn't blocked.

For performance-sensitive automation (like bulk asset processing), consider a transient workflow — a model flagged transient keeps no instance history in the repository, which makes it much faster, at the cost of no audit trail and no participant steps.

Tip: Workflows excel at human-in-the-loop and content-lifecycle processes. For purely automated, high-volume, fire-and-forget work, a Sling Job is often the better tool — it's distributed, retried, and doesn't carry workflow overhead. Use a workflow when you need steps, approvals, and history; a job when you just need reliable background processing.

Cheat sheet

NeedUseRegistered as
Design a processWorkflow model/conf/.../workflow/models
Auto-start on content changeLauncherWorkflow console / /conf
Run Java automaticallyProcess stepWorkflowProcess + process.label
Human approval/actionParticipant stepassign to user/group
Choose assignee in codeDynamic participantParticipantStepChooser + chooser.label
Branch the flowOR / AND splitmodel step
Read the contentworkItem.getWorkflowData().getPayload()
Read step argumentsargs.get("PROCESS_ARGS", String.class)
Route an OR splitwfSession.getRoutes() + complete()
High-volume automationSling Job (instead)JobConsumer

Best practices

  • ✅ Store models in /conf and deploy them as code.
  • Scope launchers tightly (path, node type, condition) and exclude the workflow service user to prevent loops.
  • ✅ Keep process steps fast; offload heavy/unreliable work to Sling Jobs.
  • ✅ Use dynamic participant steps to route approvals programmatically.
  • ✅ Use transient workflows for high-volume, no-audit automation.
  • ✅ Throw WorkflowException on failure so steps fail visibly and can retry.

Do's and Don'ts

Do

  • ✅ Check getPayloadType() is JCR_PATH before using the payload.
  • ✅ Replicate/act with a properly-permissioned service user, not admin.
  • ✅ Choose Sling Jobs over workflows for pure background processing.

Don't

  • ❌ Don't let a workflow modify the content that launched it without an exclusion — infinite loop.
  • ❌ Don't make slow external calls inline in a process step — enqueue a job.
  • ❌ Don't use participant steps in a transient workflow (unsupported).
  • ❌ Don't hard-code approver IDs when a dynamic participant chooser fits.
  • ❌ Don't forget to deploy/sync the model — runtime executes the synced copy.

Wrapping up

AEM Workflows turn content operations into repeatable, automated processes. Design the flow as a model, start it automatically with a launcher, and assemble it from process steps (your Java) and participant steps (human decisions) — with dynamic participants, splits, and containers for anything more complex. When you write a custom process, the WorkItem, WorkflowSession, and MetaDataMap give you the payload, routing, and arguments you need; keep steps fast and push heavy work to jobs. Master that and you can automate everything from a simple approval to a full content-lifecycle pipeline.

Continue with the Backend Development guide for event handlers and listeners that complement workflows, the Sling guide for when a job is the better choice, and the Assets guide for the asset-processing workflow in action.

Share this article

Subscribe to the Newsletter

Get the latest articles, tutorials, and tech insights delivered straight to your inbox. No spam, unsubscribe anytime.

Back to Blog