> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# AEM Workflows

AEM Workflows are automated, multi-step processes that model real-world business operations inside Adobe Experience Manager. They connect people, systems, and content by defining **who** needs to do **what**, in **which order**, and **under which conditions**. Whether it is a simple two-step content approval or a complex multi-department campaign launch, workflows provide a structured, auditable way to move work forward.

## Business Process Mapping

Before writing any code, it helps to think of AEM Workflows as digital versions of the processes your organisation already follows on paper, in email chains, or in project management tools.

### Common business processes

| Business process               | AEM Workflow equivalent                                      | Typical participants                        |
| ------------------------------ | ------------------------------------------------------------ | ------------------------------------------- |
| Content review and approval    | Request for Activation                                       | Author, Reviewer, Publisher                 |
| Asset ingestion and processing | DAM Update Asset                                             | Photographer/Designer, DAM Admin            |
| Translation                    | Translation workflow (via Translation Integration Framework) | Author, Translation Provider, Language Lead |
| Content decommission / sunset  | Custom unpublish workflow                                    | Content Owner, Legal, Webmaster             |
| Campaign launch                | Custom scheduled activation workflow                         | Marketing Manager, Content Team, QA         |
| Legal / compliance sign-off    | Multi-participant approval                                   | Author, Legal, Compliance Officer           |

### Example: content approval flow

A typical editorial process maps directly to an AEM Workflow:

<svg id="mermaid-svg-2073541" width="100%" xmlns="http://www.w3.org/2000/svg" style="max-width: 1353.197998046875px;" viewBox="0 0 1353.197998046875 243.0703125" role="graphics-document document" aria-roledescription="flowchart-v2"><g><marker id="mermaid-svg-2073541_flowchart-v2-pointEnd" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-pointStart" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-pointEnd-margin" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" style="stroke-width: 0; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-pointStart-margin" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" style="stroke-width: 0; stroke-dasharray: 1, 0;"></polygon></marker><marker id="mermaid-svg-2073541_flowchart-v2-circleEnd" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-2073541_flowchart-v2-circleStart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-2073541_flowchart-v2-circleEnd-margin" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-2073541_flowchart-v2-circleStart-margin" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-2073541_flowchart-v2-crossEnd" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-crossStart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-crossEnd-margin" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5;"></path></marker><marker id="mermaid-svg-2073541_flowchart-v2-crossStart-margin" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"></path></marker><g><g></g><g><path d="M256.031,136.07L260.198,136.07C264.365,136.07,272.698,136.07,280.365,136.07C288.031,136.07,295.031,136.07,298.531,136.07L302.031,136.07" id="mermaid-svg-2073541-L_Author_Submit_0" style=";" data-edge="true" data-et="edge" data-id="L_Author_Submit_0" data-points="W3sieCI6MjU2LjAzMTI1LCJ5IjoxMzYuMDcwMzIwMTI5Mzk0NTN9LHsieCI6MjgxLjAzMTI1LCJ5IjoxMzYuMDcwMzIwMTI5Mzk0NTN9LHsieCI6MzA2LjAzMTI1LCJ5IjoxMzYuMDcwMzIwMTI5Mzk0NTN9XQ==" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M529.799,99.07L537.677,96.178C545.554,93.285,561.308,87.499,572.685,84.606C584.063,81.714,591.063,81.714,594.563,81.714L598.063,81.714" id="mermaid-svg-2073541-L_Submit_Review_0" style=";" data-edge="true" data-et="edge" data-id="L_Submit_Review_0" data-points="W3sieCI6NTI5Ljc5OTMyNTYxMTI2NSwieSI6OTkuMDcwMzIwMTI5Mzk0NTN9LHsieCI6NTc3LjA2MjUsInkiOjgxLjcxMzU0Njc1MjkyOTY5fSx7IngiOjYwMi4wNjI1LCJ5Ijo4MS43MTM1NDY3NTI5Mjk2OX1d" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M840.344,81.714L844.51,81.714C848.677,81.714,857.01,81.714,864.677,81.714C872.344,81.714,879.344,81.714,882.844,81.714L886.344,81.714" id="mermaid-svg-2073541-L_Review_Approved_0" style=";" data-edge="true" data-et="edge" data-id="L_Review_Approved_0" data-points="W3sieCI6ODQwLjM0Mzc1LCJ5Ijo4MS43MTM1NDY3NTI5Mjk2OX0seyJ4Ijo4NjUuMzQzNzUsInkiOjgxLjcxMzU0Njc1MjkyOTY5fSx7IngiOjg5MC4zNDM3NTAwMDAwMDAxLCJ5Ijo4MS43MTM1NDY3NTI5Mjk2OX1d" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M1021.164,65.107L1029.987,62.541C1038.809,59.976,1056.454,54.845,1070.665,52.279C1084.875,49.714,1095.651,49.714,1101.039,49.714L1106.427,49.714" id="mermaid-svg-2073541-L_Approved_Publish_0" style=";" data-edge="true" data-et="edge" data-id="L_Approved_Publish_0" data-points="W3sieCI6MTAyMS4xNjQyMTM0NDQ1Mjk3LCJ5Ijo2NS4xMDY5MTY2OTE1OTk4NH0seyJ4IjoxMDc0LjA5ODk2ODUwNTg1OTQsInkiOjQ5LjcxMzU0Njc1MjkyOTY5fSx7IngiOjExMTAuNDI3MDkzNTA1ODU5NCwieSI6NDkuNzEzNTQ2NzUyOTI5Njl9XQ==" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M1011.206,108.278L1021.688,114.184C1032.17,120.09,1053.135,131.902,1071.169,140.478C1089.203,149.055,1104.307,154.396,1111.858,157.066L1119.41,159.737" id="mermaid-svg-2073541-L_Approved_Revise_0" style=";" data-edge="true" data-et="edge" data-id="L_Approved_Revise_0" data-points="W3sieCI6MTAxMS4yMDYxMTczNDMyMDY0LCJ5IjoxMDguMjc4MjcyOTE1NTgyODJ9LHsieCI6MTA3NC4wOTg5Njg1MDU4NTk0LCJ5IjoxNDMuNzEzNTQ2NzUyOTI5N30seyJ4IjoxMTIzLjE4MTU1NTYwMTAwNzQsInkiOjE2MS4wNzAzMjAxMjkzOTQ1M31d" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M1116.74,213.245L1109.633,214.216C1102.526,215.187,1088.313,217.128,1062.865,218.099C1037.418,219.07,1000.738,219.07,965.945,219.07C931.153,219.07,898.248,219.07,857.773,219.07C817.297,219.07,769.25,219.07,721.203,219.07C673.156,219.07,625.109,219.07,587.995,211.73C550.881,204.389,524.7,189.708,511.609,182.367L498.519,175.027" id="mermaid-svg-2073541-L_Revise_Submit_0" style=";" data-edge="true" data-et="edge" data-id="L_Revise_Submit_0" data-points="W3sieCI6MTExNi43Mzk1OTM1MDU4NTk0LCJ5IjoyMTMuMjQ0ODUzMjg5NTQ1NDN9LHsieCI6MTA3NC4wOTg5Njg1MDU4NTk0LCJ5IjoyMTkuMDcwMzIwMTI5Mzk0NTN9LHsieCI6OTY0LjA1NzI5Njc1MjkyOTcsInkiOjIxOS4wNzAzMjAxMjkzOTQ1M30seyJ4Ijo4NjUuMzQzNzUsInkiOjIxOS4wNzAzMjAxMjkzOTQ1M30seyJ4Ijo3MjEuMjAzMTI1LCJ5IjoyMTkuMDcwMzIwMTI5Mzk0NTN9LHsieCI6NTc3LjA2MjUsInkiOjIxOS4wNzAzMjAxMjkzOTQ1M30seyJ4Ijo0OTUuMDI5NzQzOTc1OTAzNiwieSI6MTczLjA3MDMyMDEyOTM5NDUzfV0=" data-look="classic" marker-end="url(#mermaid-svg-2073541_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path></g><g><g><g data-id="L_Author_Submit_0" transform="translate(0, 0)"></g></g><g><g data-id="L_Submit_Review_0" transform="translate(0, 0)"></g></g><g><g data-id="L_Review_Approved_0" transform="translate(0, 0)"></g></g><g transform="translate(1074.0989685058594, 49.71354675292969)"><g data-id="L_Approved_Publish_0" transform="translate(-11.328125, -22)"><foreignObject width="22.65625" height="44"><p>Yes</p></foreignObject></g></g><g transform="translate(1065.33118, 138.77358)"><g data-id="L_Approved_Revise_0" transform="translate(-9.401041984558105, -22)"><foreignObject width="18.80208396911621" height="44"><p>No</p></foreignObject></g></g><g><g data-id="L_Revise_Submit_0" transform="translate(0, 0)"></g></g></g><g><g id="mermaid-svg-2073541-flowchart-Author-0" data-look="classic" transform="translate(132.015625, 136.07032012939453)"><rect style="" x="-124.015625" y="-37" width="248.03125" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-94.015625, -22)"><rect></rect><foreignObject width="188.03125" height="44"><p>Author creates/edits page</p></foreignObject></g></g><g id="mermaid-svg-2073541-flowchart-Submit-1" data-look="classic" transform="translate(429.046875, 136.07032012939453)"><rect style="" x="-123.015625" y="-37" width="246.03125" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-93.015625, -22)"><rect></rect><foreignObject width="186.03125" height="44"><p>Author submits for review</p></foreignObject></g></g><g id="mermaid-svg-2073541-flowchart-Review-2" data-look="classic" transform="translate(721.203125, 81.71354675292969)"><rect style="" x="-119.140625" y="-37" width="238.28125" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-89.140625, -22)"><rect></rect><foreignObject width="178.28125" height="44"><p>Reviewer checks content</p></foreignObject></g></g><g id="mermaid-svg-2073541-flowchart-Approved-3" data-look="classic" transform="translate(964.0572967529297, 81.71354675292969)"><polygon points="73.71354293823242,0 147.42708587646484,-73.71354293823242 73.71354293823242,-147.42708587646484 0,-73.71354293823242" transform="translate(-73.21354293823242, 73.71354293823242)" fill="none" stroke="currentColor"></polygon><g style="" transform="translate(-36.71354293823242, -22)"><rect></rect><foreignObject width="73.42708587646484" height="44"><p>Approved?</p></foreignObject></g></g><g id="mermaid-svg-2073541-flowchart-Publish-4" data-look="classic" transform="translate(1227.8125076293945, 49.71354675292969)"><rect style="" x="-117.38542175292969" y="-37" width="234.77084350585938" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-87.38542175292969, -22)"><rect></rect><foreignObject width="174.77084350585938" height="44"><p>Publisher activates page</p></foreignObject></g></g><g id="mermaid-svg-2073541-flowchart-Revise-5" data-look="classic" transform="translate(1227.8125076293945, 198.07032012939453)"><rect style="" x="-111.07292175292969" y="-37" width="222.14584350585938" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-81.07292175292969, -22)"><rect></rect><foreignObject width="162.14584350585938" height="44"><p>Author revises content</p></foreignObject></g></g></g></g></g><defs></defs><defs></defs><linearGradient id="mermaid-svg-2073541-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#cccccc" stop-opacity="1"></stop><stop offset="100%" stop-color="hsl(180, 0%, 18.3529411765%)" stop-opacity="1"></stop></linearGradient></svg>

In AEM terms:

- **Author** triggers the workflow (manually or via a launcher)
- **Reviewer** receives a task in their **AEM Inbox**
- **Approved / Rejected** is modelled as an **OR Split** with two routes
- **Publisher** activates the page via a **Process Step** that calls the Replicator service
- **Revise** loops back to the author via a **Goto Step**

### Workflow building blocks in business terms

| Building block  | What it means                                         | Example                             |
| --------------- | ----------------------------------------------------- | ----------------------------------- |
| **Step**        | A single unit of work                                 | "Review the page"                   |
| **Participant** | A person or group responsible for a step              | "content-reviewers" group           |
| **Route**       | The path work takes after a step completes            | "Approve" or "Reject"               |
| **Payload**     | The content the workflow acts on                      | `/content/my-site/en/about`         |
| **OR Split**    | A decision point with multiple routes                 | "Is the content approved?"          |
| **AND Split**   | Parallel work streams that must all complete          | "Legal review AND marketing review" |
| **Timeout**     | An automatic action if a step isn't completed in time | "Auto-escalate after 3 days"        |

***

## AEM Workflow Concepts

This section bridges the business view and the technical implementation.

### Workflow models

A workflow **model** is the blueprint - the reusable definition of steps, routes, and logic. Models are stored in the JCR under:

```markdown
/var/workflow/models/         <-- runtime (compiled) models
/conf/global/settings/workflow/models/   <-- editable model definitions
```

Each model has a unique ID (e.g. `/var/workflow/models/request_for_activation`) and can be versioned. When you edit a model in the Workflow Editor, AEM creates a new version and syncs it to `/var/workflow/models/`.

### Workflow instances

When a workflow model is **started** against a payload, AEM creates a workflow **instance** - a running copy that tracks the current step, participant assignments, and metadata.

```markdown
/var/workflow/instances/         <-- active (running) instances
/var/workflow/instances/server0/  <-- clustered: partitioned by server
```

Completed instances are moved to an archive area and can be purged for performance.

### Workflow lifecycle

<svg id="mermaid-svg-6953189" width="100%" xmlns="http://www.w3.org/2000/svg" style="max-width: 464.7203063964844px;" viewBox="0 0 464.7203063964844 836.1499633789062" role="graphics-document document" aria-roledescription="flowchart-v2"><g><marker id="mermaid-svg-6953189_flowchart-v2-pointEnd" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-pointStart" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-pointEnd-margin" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" style="stroke-width: 0; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-pointStart-margin" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" style="stroke-width: 0; stroke-dasharray: 1, 0;"></polygon></marker><marker id="mermaid-svg-6953189_flowchart-v2-circleEnd" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-6953189_flowchart-v2-circleStart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-6953189_flowchart-v2-circleEnd-margin" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-6953189_flowchart-v2-circleStart-margin" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-6953189_flowchart-v2-crossEnd" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-crossStart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-crossEnd-margin" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5;"></path></marker><marker id="mermaid-svg-6953189_flowchart-v2-crossStart-margin" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"></path></marker><g><g></g><g><path d="M326.72,82L326.72,89.833C326.72,97.667,326.72,113.333,326.72,128.333C326.72,143.333,326.72,157.667,326.72,164.833L326.72,172" id="mermaid-svg-6953189-L_Model_Launcher_0" style=";" data-edge="true" data-et="edge" data-id="L_Model_Launcher_0" data-points="W3sieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5Ijo4Mn0seyJ4IjozMjYuNzIwMzEyNTAwMzcyNTMsInkiOjEyOX0seyJ4IjozMjYuNzIwMzEyNTAwMzcyNTMsInkiOjE3Nn1d" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M326.72,250L326.72,257.833C326.72,265.667,326.72,281.333,326.72,296.333C326.72,311.333,326.72,325.667,326.72,332.833L326.72,340" id="mermaid-svg-6953189-L_Launcher_Instance_0" style=";" data-edge="true" data-et="edge" data-id="L_Launcher_Instance_0" data-points="W3sieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5IjoyNTB9LHsieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5IjoyOTd9LHsieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5IjozNDR9XQ==" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M326.72,442L326.72,446.167C326.72,450.333,326.72,458.667,326.72,466.333C326.72,474,326.72,481,326.72,484.5L326.72,488" id="mermaid-svg-6953189-L_Instance_Steps_0" style=";" data-edge="true" data-et="edge" data-id="L_Instance_Steps_0" data-points="W3sieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5Ijo0NDJ9LHsieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5Ijo0Njd9LHsieCI6MzI2LjcyMDMxMjUwMDM3MjUzLCJ5Ijo0OTJ9XQ==" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M243.283,557.054L215.551,566.378C187.819,575.703,132.355,594.351,104.623,610.842C76.891,627.333,76.891,641.667,76.891,648.833L76.891,656" id="mermaid-svg-6953189-L_Steps_Complete_0" style=";" data-edge="true" data-et="edge" data-id="L_Steps_Complete_0" data-points="W3sieCI6MjQzLjI4MjgxMjUwMDM3MjUzLCJ5Ijo1NTcuMDU0MTExODYzNjY1M30seyJ4Ijo3Ni44OTA2MjUsInkiOjYxM30seyJ4Ijo3Ni44OTA2MjUsInkiOjY2MH1d" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M300.147,566L294.521,573.833C288.895,581.667,277.643,597.333,272.017,612.333C266.391,627.333,266.391,641.667,266.391,648.833L266.391,656" id="mermaid-svg-6953189-L_Steps_Terminated_0" style=";" data-edge="true" data-et="edge" data-id="L_Steps_Terminated_0" data-points="W3sieCI6MzAwLjE0NjUyMTU3NzU4OTQsInkiOjU2Nn0seyJ4IjoyNjYuMzkwNjI1LCJ5Ijo2MTN9LHsieCI6MjY2LjM5MDYyNSwieSI6NjYwfV0=" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M309.059,566L306.409,571.55C303.76,577.1,298.462,588.2,306.998,593.75C315.535,599.3,337.906,599.3,346.73,594.352C355.553,589.403,350.829,579.507,348.467,574.558L346.105,569.61" id="mermaid-svg-6953189-L_Steps_Steps_0" style=";" data-edge="true" data-et="edge" data-id="L_Steps_Steps_0" data-points="W3sieCI6MzA5LjA1ODU3OTM3NDk4NzA2LCJ5Ijo1NjZ9LHsieCI6MjkzLjE2MzAxOTU2MjE0MDEsInkiOjU5OS4zfSx7IngiOjM2MC4yNzc2MDU0Mzg2MDQ5NSwieSI6NTk5LjN9LHsieCI6MzQ0LjM4MjA0NTYyNTc1OCwieSI6NTY2fV0=" data-look="classic" marker-end="url(#mermaid-svg-6953189_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path></g><g><g transform="translate(326.72031250037253, 129)"><g data-id="L_Model_Launcher_0" transform="translate(-43.703125, -22)"><foreignObject width="87.40625" height="44"><p>triggered by</p></foreignObject></g></g><g transform="translate(326.72031250037253, 297)"><g data-id="L_Launcher_Instance_0" transform="translate(-26.41145896911621, -22)"><foreignObject width="52.82291793823242" height="44"><p>creates</p></foreignObject></g></g><g><g data-id="L_Instance_Steps_0" transform="translate(0, 0)"></g></g><g transform="translate(76.890625, 613)"><g data-id="L_Steps_Complete_0" transform="translate(-49.69791793823242, -22)"><foreignObject width="99.39583587646484" height="44"><p>all steps done</p></foreignObject></g></g><g transform="translate(266.390625, 613)"><g data-id="L_Steps_Terminated_0" transform="translate(-46.645835876464844, -22)"><foreignObject width="93.29167175292969" height="44"><p>admin action</p></foreignObject></g></g><g transform="translate(326.72031250037253, 625.3)"><g data-id="L_Steps_Steps_0" transform="translate(-33.55729293823242, -22)"><foreignObject width="67.11458587646484" height="44"><p>next step</p></foreignObject></g></g></g><g><g id="mermaid-svg-6953189-flowchart-Model-0" data-look="classic" transform="translate(326.72031250037253, 45)"><rect style="" x="-128.234375" y="-37" width="256.46875" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-98.234375, -22)"><rect></rect><foreignObject width="196.46875" height="44"><p>Workflow Model (blueprint)</p></foreignObject></g></g><g id="mermaid-svg-6953189-flowchart-Launcher-1" data-look="classic" transform="translate(326.72031250037253, 213)"><rect style="" x="-127.65104675292969" y="-37" width="255.30209350585938" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-97.65104675292969, -22)"><rect></rect><foreignObject width="195.30209350585938" height="44"><p>Launcher or manual trigger</p></foreignObject></g></g><g id="mermaid-svg-6953189-flowchart-Instance-2" data-look="classic" transform="translate(326.72031250037253, 393)"><rect style="" x="-130" y="-49" width="260" height="98" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-100, -34)"><rect></rect><foreignObject width="200" height="68"><p>Workflow Instance (running)</p></foreignObject></g></g><g id="mermaid-svg-6953189-flowchart-Steps-3" data-look="classic" transform="translate(326.72031250037253, 529)"><rect style="" x="-83.4375" y="-37" width="166.875" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-53.4375, -22)"><rect></rect><foreignObject width="106.875" height="44"><p>Step execution</p></foreignObject></g></g><g id="mermaid-svg-6953189-flowchart-Complete-4" data-look="classic" transform="translate(76.890625, 697)"><rect style="" x="-68.890625" y="-37" width="137.78125" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-38.890625, -22)"><rect></rect><foreignObject width="77.78125" height="44"><p>Completed</p></foreignObject></g></g><g id="mermaid-svg-6953189-flowchart-Terminated-5" data-look="classic" transform="translate(266.390625, 697)"><rect style="" x="-70.609375" y="-37" width="141.21875" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-40.609375, -22)"><rect></rect><foreignObject width="81.21875" height="44"><p>Terminated</p></foreignObject></g></g><g id="Steps---Steps---1" transform="translate(387.05000000074506, 697)"><rect width="0.1" height="0.1"></rect><g style="" transform="translate(0, 0)"><rect></rect></g></g><g id="Steps---Steps---2" transform="translate(413.82864646986127, 828.0500000007451)"><rect width="0.1" height="0.1"></rect><g style="" transform="translate(0, 0)"><rect></rect></g></g></g></g></g><defs></defs><defs></defs><linearGradient id="mermaid-svg-6953189-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#cccccc" stop-opacity="1"></stop><stop offset="100%" stop-color="hsl(180, 0%, 18.3529411765%)" stop-opacity="1"></stop></linearGradient></svg>

| State         | Description                                                            |
| ------------- | ---------------------------------------------------------------------- |
| **RUNNING**   | The instance is active, waiting for a step to complete                 |
| **COMPLETED** | All steps finished successfully                                        |
| **ABORTED**   | The workflow was terminated by an administrator                        |
| **SUSPENDED** | The workflow is paused and can be resumed                              |
| **STALE**     | The workflow instance references a model version that no longer exists |

### Workflow launchers

Launchers **automatically start** a workflow when a JCR event matches certain criteria. They are configured at:

```markdown
/conf/global/settings/workflow/launcher/config/
```

A launcher watches for:

| Property      | Purpose                                             | Example                                       |
| ------------- | --------------------------------------------------- | --------------------------------------------- |
| `eventType`   | JCR event type (1 = node added, 16 = node modified) | `{Long}16`                                    |
| `nodetype`    | JCR node type to match                              | `cq:PageContent`                              |
| `glob`        | Path pattern to match                               | `/content/my-site/.*`                         |
| `workflow`    | The workflow model to start                         | `/var/workflow/models/request_for_activation` |
| `runModes`    | Run modes where the launcher is active              | `author`                                      |
| `condition`   | Additional condition expression                     | `jcr:content/cq:template==/conf/.../page`     |
| `excludeList` | Properties to ignore (prevents re-trigger loops)    | `jcr:lastModified,cq:lastModified`            |
| `enabled`     | Whether the launcher is active                      | `{Boolean}true`                               |

> \[!-warning] -warning
> warning
>
> Without a proper `excludeList`, a workflow that modifies the payload node will re-trigger its own launcher, creating an **infinite loop**. Always exclude metadata properties that your workflow writes.

### Participants and the AEM Inbox

When a workflow reaches a **Participant Step**, AEM creates a work item in the assigned user's or group's **Inbox** (accessible at `/aem/inbox`). The participant can:

- **Complete** the step (advance the workflow)
- **Delegate** the step to another user
- **Step back** to a previous step
- Add **comments** for audit trail

Participants can be assigned as:

- A specific **user** (`admin`, `jdoe`)
- A **group** (`content-authors`, `reviewers`)
- **Dynamic** via a `ParticipantStepChooser` (programmatic assignment at runtime)

***

## Building Workflows in the AEM UI

### Workflow Model Editor

Navigate to **Tools > Workflow > Models** to create or edit workflow models. The visual editor lets you drag and drop steps onto a canvas and connect them with transitions.

Steps to create a new model:

1. Click **Create > Create Model**
2. Enter a title and name (the name becomes the model ID)
3. Click the model to open the editor
4. Drag steps from the side panel onto the canvas
5. Connect steps by dragging from one step's output to another step's input
6. Configure each step by double-clicking it
7. Click **Sync** to publish the model to `/var/workflow/models/`

> \[!-success] -success
> tip
>
> Always click **Sync** after editing. The workflow editor works on a draft version - changes are not active until you sync.

### Workflow step types

| Step type                    | Purpose                                                                                     | Assignment               |
| ---------------------------- | ------------------------------------------------------------------------------------------- | ------------------------ |
| **Participant Step**         | Assigns a task to a user or group via the Inbox                                             | Static user/group        |
| **Dialog Participant Step**  | Like Participant Step, but opens a dialog for the participant to fill in data               | Static user/group + form |
| **Dynamic Participant Step** | Routes to a user/group determined at runtime by a `ParticipantStepChooser`                  | Programmatic             |
| **Process Step**             | Executes a Java `WorkflowProcess` implementation automatically                              | None (system)            |
| **OR Split**                 | Decision point: evaluates conditions on each branch, follows the first matching route       | Rule-based               |
| **AND Split / AND Join**     | Splits into parallel branches that all execute concurrently, then waits for all to complete | Parallel                 |
| **Container Step**           | Groups steps into a sub-workflow for organisational clarity                                 | N/A                      |
| **Goto Step**                | Jumps to a previous step (creates a loop)                                                   | N/A                      |
| **External Process Step**    | Calls an external system and waits for a callback                                           | External                 |

### Configuring launchers in the UI

Navigate to **Tools > Workflow > Launchers** to manage automatic triggers:

1. Click **Create** to add a new launcher
2. Select the **Event Type** (Node Created, Node Modified, etc.)
3. Enter a **Glob** pattern for the content path
4. Select the **Workflow Model** to trigger
5. Optionally add a **Condition** and **Exclude List**
6. Set the **Run Modes** (typically `author`)

***

## Custom Workflow Steps (Java)

### Custom Process Step

A Process Step executes Java code automatically when the workflow reaches it. Implement the `WorkflowProcess` interface:

```java
package com.myproject.core.workflow;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(
    service = WorkflowProcess.class,
    property = {
        "process.label=My Project - Auto Tag Pages"
    }
)
public class AutoTagProcessStep implements WorkflowProcess {

    private static final Logger LOG = LoggerFactory.getLogger(AutoTagProcessStep.class);

    @Override
    public void execute(WorkItem workItem, WorkflowSession workflowSession,
                        MetaDataMap metaDataMap) throws WorkflowException {

        // 1. Get the payload path
        String payloadPath = workItem.getWorkflowData().getPayload().toString();
        LOG.info("Processing payload: {}", payloadPath);

        // 2. Read process step arguments (configured in the workflow model editor)
        String tagNamespace = metaDataMap.get("PROCESS_ARGS", "myproject:");

        // 3. Access the JCR via the workflow session's resource resolver
        ResourceResolver resolver = workflowSession.adaptTo(ResourceResolver.class);
        if (resolver == null) {
            throw new WorkflowException("Could not obtain ResourceResolver");
        }

        Resource contentResource = resolver.getResource(payloadPath + "/jcr:content");
        if (contentResource == null) {
            LOG.warn("No jcr:content found at {}", payloadPath);
            return;
        }

        // 4. Apply business logic
        ModifiableValueMap properties = contentResource.adaptTo(ModifiableValueMap.class);
        if (properties != null) {
            String title = properties.get("jcr:title", "");
            // Example: auto-tag based on title keywords
            if (title.toLowerCase().contains("news")) {
                String[] existingTags = properties.get("cq:tags", new String[0]);
                String newTag = tagNamespace + "content-type/news";
                if (!java.util.Arrays.asList(existingTags).contains(newTag)) {
                    String[] updatedTags = java.util.Arrays.copyOf(
                        existingTags, existingTags.length + 1);
                    updatedTags[existingTags.length] = newTag;
                    properties.put("cq:tags", updatedTags);
                    LOG.info("Tagged {} with {}", payloadPath, newTag);
                }
            }
            resolver.commit();
        }
    }
}
```

The `process.label` OSGi property is the name that appears in the workflow model editor's step configuration dropdown.

:::info Process Step Arguments You can pass arguments to a Process Step in the model editor. They are available via `metaDataMap.get("PROCESS_ARGS", String.class)`. For multiple arguments, use a delimiter (e.g. comma-separated) and parse them in your code.:::

### Accessing the payload

The payload can be a page path, an asset path, or a JCR path. Here's how to access it safely:

```java
// Get the raw payload (usually a String path)
String payloadPath = workItem.getWorkflowData().getPayload().toString();

// Get the payload type
String payloadType = workItem.getWorkflowData().getPayloadType();
// Common values: "JCR_PATH", "JCR_UUID", "URL"

// Adapt to a resource
ResourceResolver resolver = workflowSession.adaptTo(ResourceResolver.class);
Resource resource = resolver.getResource(payloadPath);

// Adapt to a Page
PageManager pageManager = resolver.adaptTo(PageManager.class);
Page page = pageManager.getPage(payloadPath);

// Adapt to an Asset
Asset asset = resource.adaptTo(Asset.class);
```

### Custom Participant Chooser

A `ParticipantStepChooser` dynamically determines who should handle a workflow step at runtime. This is used with **Dynamic Participant Steps**.

```java
package com.myproject.core.workflow;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.ParticipantStepChooser;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(
    service = ParticipantStepChooser.class,
    property = {
        "chooser.label=My Project - Content Owner Chooser"
    }
)
public class ContentOwnerChooser implements ParticipantStepChooser {

    private static final Logger LOG = LoggerFactory.getLogger(ContentOwnerChooser.class);
    private static final String DEFAULT_GROUP = "content-authors";

    @Override
    public String getParticipant(WorkItem workItem, WorkflowSession workflowSession,
                                  MetaDataMap metaDataMap) throws WorkflowException {

        String payloadPath = workItem.getWorkflowData().getPayload().toString();
        ResourceResolver resolver = workflowSession.adaptTo(ResourceResolver.class);

        if (resolver != null) {
            Resource jcrContent = resolver.getResource(payloadPath + "/jcr:content");
            if (jcrContent != null) {
                ValueMap props = jcrContent.getValueMap();
                // Route to the page's designated reviewer if set
                String reviewer = props.get("reviewer", String.class);
                if (reviewer != null && !reviewer.isEmpty()) {
                    LOG.info("Routing {} to designated reviewer: {}", payloadPath, reviewer);
                    return reviewer;
                }
            }
        }

        // Fallback to a default group
        LOG.info("No designated reviewer for {}, routing to {}", payloadPath, DEFAULT_GROUP);
        return DEFAULT_GROUP;
    }
}
```

The `chooser.label` appears in the Dynamic Participant Step configuration in the model editor.

### Custom OR Split logic

An OR Split routes execution to one of several branches based on a condition. The typical pattern is:

1. A **Process Step** before the OR Split writes a boolean variable into the workflow's `MetaDataMap`.
2. Each OR Split branch has a **Script** (or **Rule**) expression that reads the variable and returns `true` / `false`.

The Process Step is a normal `WorkflowProcess`:

```java
package com.myproject.core.workflow;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.service.component.annotations.Component;

@Component(
    service = WorkflowProcess.class,
    property = {
        "process.label=My Project - Set Publish Ready Flag"
    }
)
public class PublishReadyCheckStep implements WorkflowProcess {

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

        WorkflowData workflowData = workItem.getWorkflowData();
        Object payload = workflowData != null ? workflowData.getPayload() : null;
        if (payload == null || !"JCR_PATH".equals(workflowData.getPayloadType())) {
            throw new WorkflowException("Expected a JCR_PATH payload");
        }
        String payloadPath = payload.toString();

        ResourceResolver resolver = session.adaptTo(ResourceResolver.class);
        if (resolver == null) {
            throw new WorkflowException("Could not obtain ResourceResolver");
        }

        Resource jcrContent = resolver.getResource(payloadPath + "/jcr:content");
        boolean publishReady = false;
        if (jcrContent != null) {
            ValueMap props = jcrContent.getValueMap();
            publishReady = props.containsKey("jcr:title")
                && props.containsKey("jcr:description")
                && props.containsKey("featuredImage");
        }

        // Store the result so the OR Split branches can read it
        workItem.getWorkflow().getWorkflowData().getMetaDataMap()
            .put("publishReady", publishReady);
    }
}
```

In the OR Split, the branches must be complementary, not identical. Give the "ready" branch a routing expression type of **Script**, with the language set to **ECMA Script**, and paste the function into the inline script field:

```markdown
function check() { return workflowData.getMetaDataMap().get("publishReady", false); }
```

Mark the other branch as the **Default** route (taken when no other branch's condition matches), so exactly one branch fires. If you need an explicit condition on both sides instead of a default, negate the check on the second branch: `return !workflowData.getMetaDataMap().get("publishReady", false);`

**Script Path** is a separate option that points at a repository node holding the same code as an external `.ecma` file, rather than accepting inline text.

***

## Workflow API (Programmatic)

### Starting a workflow

```java
@Reference
private com.adobe.granite.workflow.WorkflowService workflowService;

public void startApprovalWorkflow(ResourceResolver resolver, String pagePath)
        throws WorkflowException {

    WorkflowSession wfSession = workflowService.getWorkflowSession(
        resolver.adaptTo(Session.class));

    // Load the workflow model
    WorkflowModel model = wfSession.getModel("/var/workflow/models/request_for_activation");

    // Create the workflow data (payload)
    WorkflowData data = wfSession.newWorkflowData("JCR_PATH", pagePath);

    // Optionally add metadata
    data.getMetaDataMap().put("comment", "Submitted for review");
    data.getMetaDataMap().put("urgency", "high");

    // Start the workflow
    wfSession.startWorkflow(model, data);
}
```

### Querying workflow status

```java
public List<String> getRunningWorkflowIds(ResourceResolver resolver) throws WorkflowException {
    WorkflowSession wfSession = resolver.adaptTo(WorkflowSession.class);
    Workflow[] allWorkflows = wfSession.getAllWorkflows();

    List<String> runningIds = new ArrayList<>();
    for (Workflow wf : allWorkflows) {
        if ("RUNNING".equals(wf.getState())) {
            runningIds.add(wf.getId());
        }
    }
    return runningIds;
}
```

### Terminating running workflows

```java
/**
 * Terminates all running workflow instances visible to the given resolver.
 * Use with caution - this is typically an admin operation.
 */
public int terminateAllRunningWorkflows(ResourceResolver resolver) {
    WorkflowSession workflowSession = resolver.adaptTo(WorkflowSession.class);
    if (workflowSession == null) {
        LOG.error("Could not obtain WorkflowSession");
        return 0;
    }

    int terminated = 0;
    try {
        Workflow[] allWorkflows = workflowSession.getAllWorkflows();
        for (Workflow wf : allWorkflows) {
            if ("RUNNING".equals(wf.getState())) {
                String id = wf.getId();
                try {
                    workflowSession.terminateWorkflow(wf);
                    terminated++;
                    LOG.info("Terminated workflow: {}", id);
                } catch (WorkflowException e) {
                    LOG.error("Could not terminate workflow {}", id, e);
                }
            }
        }
    } catch (WorkflowException e) {
        LOG.error("Error retrieving workflows", e);
    }
    return terminated;
}
```

### Managing launchers programmatically

```java
/**
 * Disables all workflow launchers. Useful during bulk imports or migrations
 * to prevent unwanted workflow triggers.
 */
public void disableAllLaunchers(ResourceResolver resolver) throws LoginException {
    WorkflowLauncher launcherService = resolver.adaptTo(WorkflowLauncher.class);
    if (launcherService == null) {
        LOG.error("Could not obtain WorkflowLauncher service");
        return;
    }

    Iterator<ConfigEntry> entries = launcherService.getConfigEntries();
    while (entries.hasNext()) {
        ConfigEntry entry = entries.next();
        if (entry.isEnabled()) {
            entry.setEnabled(false);
            LOG.info("Disabled launcher: {}", entry.getId());
        }
    }
}

/**
 * Re-enables all workflow launchers.
 */
public void enableAllLaunchers(ResourceResolver resolver) {
    WorkflowLauncher launcherService = resolver.adaptTo(WorkflowLauncher.class);
    if (launcherService == null) return;

    Iterator<ConfigEntry> entries = launcherService.getConfigEntries();
    while (entries.hasNext()) {
        ConfigEntry entry = entries.next();
        if (!entry.isEnabled()) {
            entry.setEnabled(true);
            LOG.info("Enabled launcher: {}", entry.getId());
        }
    }
}
```

> \[!-success] -success
> tip
>
> A common pattern during bulk content migrations: disable launchers before the import, run the import, then re-enable launchers. This avoids thousands of workflow instances being created for every imported node.

### Disabling a launcher via overlay

Instead of programmatic toggling, you can permanently disable a built-in launcher by overlaying its configuration. This is deployed as part of your code package and survives instance restarts.

Create an overlay at `/conf/global/settings/workflow/launcher/config/<launcher-name>/.content.xml` with `enabled="{Boolean}false"`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
          xmlns:cq="http://www.day.com/jcr/cq/1.0"
    jcr:primaryType="cq:WorkflowLauncher"
    comment="Disabled - we use Adobe Campaign for newsletters"
    enabled="{Boolean}false"
    eventType="{Long}1"
    excludeList=""
    glob="/var/mailimport"
    nodetype="nt:unstructured"
    workflow="/var/workflow/models/newsletter_bounce_check"/>
```

Make sure this path is included in your package's `META-INF/vault/filter.xml`:

```xml
<filter root="/conf/global/settings/workflow/launcher/config/newsletter_bounce_check"/>
```

***

## DAM Asset Workflows

### DAM Update Asset workflow

The **DAM Update Asset** workflow is AEM's built-in pipeline for processing uploaded assets. It runs automatically when a new asset is added or an existing asset is replaced.

Default steps include:

1. **Metadata extraction** - reads EXIF, IPTC, XMP data from the file
2. **Rendition generation** - creates thumbnails and web-optimized renditions
3. **Sub-asset extraction** - extracts pages from PDFs, slides from presentations
4. **XMP writeback** - writes AEM metadata changes back into the binary
5. **Full-text extraction** - indexes text content for search

### Customising DAM workflows

You can add custom steps to the DAM Update Asset workflow for project-specific processing:

```java
@Component(
    service = WorkflowProcess.class,
    property = {
        "process.label=My Project - Add Watermark Rendition"
    }
)
public class WatermarkRenditionStep implements WorkflowProcess {

    @Override
    public void execute(WorkItem workItem, WorkflowSession workflowSession,
                        MetaDataMap metaDataMap) throws WorkflowException {

        String payloadPath = workItem.getWorkflowData().getPayload().toString();
        ResourceResolver resolver = workflowSession.adaptTo(ResourceResolver.class);
        Resource assetResource = resolver.getResource(payloadPath);

        if (assetResource == null) return;

        Asset asset = assetResource.adaptTo(Asset.class);
        if (asset == null) return;

        // Get the original rendition
        Rendition original = asset.getOriginal();
        if (original == null) return;

        // Example: create a watermarked rendition
        // (actual image processing code omitted for brevity)
        try (InputStream watermarked = applyWatermark(original.getStream())) {
            asset.addRendition("cq5dam.watermark.png", watermarked, "image/png");
            resolver.commit();
        } catch (Exception e) {
            throw new WorkflowException("Watermark generation failed", e);
        }
    }

    private InputStream applyWatermark(InputStream original) {
        // Image processing logic using Java2D, ImageMagick, etc.
        return original; // placeholder
    }
}
```

### AEM as a Cloud Service: Asset microservices

:::warning AEMaaCS On AEM as a Cloud Service, the DAM Update Asset workflow has been replaced by **Asset microservices**. Custom Java workflow steps for rendition generation **do not run** on AEMaaCS. Instead:

- Rendition generation is handled by Adobe's cloud-native microservices
- Custom processing uses **Processing Profiles** (configured in the DAM admin UI)
- For advanced custom processing, use **Asset Compute workers** (Adobe I/O Runtime)
- **Post-processing workflows** can still run custom Java steps for metadata operations, but not binary manipulation

If you are building for AEMaaCS, plan your DAM workflow customisations around Processing Profiles and Asset Compute rather than custom `WorkflowProcess` steps.:::

***

## Built-in Workflows Reference

AEM ships with several commonly used workflows:

| Workflow                     | Model path                                          | Purpose                         |
| ---------------------------- | --------------------------------------------------- | ------------------------------- |
| **Request for Activation**   | `/var/workflow/models/request_for_activation`       | Content approval before publish |
| **DAM Update Asset**         | `/var/workflow/models/dam/update_asset`             | Asset processing pipeline       |
| **DAM Parse Word Documents** | `/var/workflow/models/dam/dam-parse-word-documents` | Extract content from Word files |
| **Publish Page**             | `/var/workflow/models/publish_example`              | Automated page publishing       |
| **Unpublish Page**           | `/var/workflow/models/unpublish_example`            | Automated page deactivation     |
| **Scheduled Activation**     | `/var/workflow/models/scheduled_activation`         | Time-delayed activation         |
| **Scheduled Deactivation**   | `/var/workflow/models/scheduled_deactivation`       | Time-delayed deactivation       |
| **Translation**              | `/var/workflow/models/translation`                  | Content translation pipeline    |

***

## Best Practices and Common Pitfalls

### Use transient workflows for high-volume processing

Transient workflows don't persist intermediate step data to the JCR. This dramatically improves performance for workflows that process many items (like DAM Update Asset).

Enable transient mode by checking **Transient** in the workflow model editor's page properties.

| Mode          | JCR writes                                | Performance | Audit trail  |
| ------------- | ----------------------------------------- | ----------- | ------------ |
| **Normal**    | Every step change is persisted            | Slower      | Full history |
| **Transient** | Only payload and completion are persisted | Faster      | Limited      |

### Purge completed workflow instances

Completed workflow instances accumulate in `/var/workflow/instances/` and degrade repository performance over time. Configure regular purging:

- **AEM UI**: Tools > Workflow > Instances > select completed > Purge
- **Maintenance tasks**: Configure the **Workflow Purge** maintenance task in the Operations Dashboard
- **Recommended retention**: 30 days or fewer for completed instances

### Prevent launcher infinite loops

If your workflow modifies the payload (e.g. sets a property), the modification can re-trigger the launcher, creating an infinite loop. Prevent this by:

1. **`excludeList`** - list properties that should not re-trigger the launcher:

```markdown
jcr:lastModified,cq:lastModified,jcr:lastModifiedBy,cq:lastRolledout
```

2. **Conditions** - add a condition to the launcher that checks for a "processed" flag
3. **Workflow logic** - have your process step set a flag property, and check for it before re-processing

### Avoid long-running process steps

Workflow process steps run on the AEM server thread pool. A step that takes minutes (e.g. calling a slow external API) blocks the thread and can starve the workflow engine.

For long-running operations:

- Use a **Sling Job** (via `JobManager`) and have the workflow step start the job, then use an **External Process Step** that waits for a callback
- Set timeouts on HTTP calls
- Consider asynchronous patterns with a polling step

### Use service users for workflow operations

Workflow steps that need elevated permissions should use a dedicated **service user** rather than relying on the initiator's session:

```java
@Reference
private ResourceResolverFactory resolverFactory;

private ResourceResolver getServiceResolver() throws LoginException {
    Map<String, Object> params = new HashMap<>();
    params.put(ResourceResolverFactory.SUBSERVICE, "workflow-service");
    return resolverFactory.getServiceResourceResolver(params);
}
```

Configure the service user mapping in your OSGi configuration:

```json
{
    "user.mapping": [
        "com.myproject.core:workflow-service=myproject-workflow-service"
    ]
}
```

### Test workflow steps in isolation

Write unit tests for your `WorkflowProcess` implementations using mocked `WorkItem`, `WorkflowSession`, and `MetaDataMap` objects:

```java
@ExtendWith(AemContextExtension.class)
class AutoTagProcessStepTest {

    private final AemContext context = new AemContext();

    @Test
    void shouldTagNewsPages() throws WorkflowException {
        // Setup
        context.create().page("/content/test", "/conf/template",
            Map.of("jcr:title", "Breaking News Today"));

        WorkItem workItem = mock(WorkItem.class);
        WorkflowData workflowData = mock(WorkflowData.class);
        when(workItem.getWorkflowData()).thenReturn(workflowData);
        when(workflowData.getPayload()).thenReturn("/content/test");

        WorkflowSession wfSession = mock(WorkflowSession.class);
        when(wfSession.adaptTo(ResourceResolver.class))
            .thenReturn(context.resourceResolver());

        MetaDataMap metaData = new SimpleMetaDataMap();
        metaData.put("PROCESS_ARGS", "myproject:");

        // Execute
        AutoTagProcessStep step = new AutoTagProcessStep();
        step.execute(workItem, wfSession, metaData);

        // Verify
        Resource jcrContent = context.resourceResolver()
            .getResource("/content/test/jcr:content");
        String[] tags = jcrContent.getValueMap().get("cq:tags", new String[0]);
        assertTrue(Arrays.asList(tags).contains("myproject:content-type/news"));
    }
}
```

### AEMaaCS-specific considerations

| Feature                     | AEM 6.5 (on-prem/AMS) | AEM as a Cloud Service            |
| --------------------------- | --------------------- | --------------------------------- |
| Custom DAM processing steps | Supported             | Not supported - use Asset Compute |
| Workflow model editor       | Available             | Available                         |
| Custom process steps        | Supported             | Supported (for non-DAM workflows) |
| Workflow launchers          | File-based and UI     | Same                              |
| Transient workflows         | Supported             | Supported                         |
| Workflow purge              | Manual or scheduled   | Automatic (maintenance windows)   |

***

## External Resources

- [Adobe: Administering Workflows](https://experienceleague.adobe.com/en/docs/experience-manager-65/content/sites/administering/operations/workflows)
- [Adobe: Developing and Extending Workflows](https://experienceleague.adobe.com/en/docs/experience-manager-65/content/sites/administering/operations/workflows)
- [Adobe: Asset Microservices (AEMaaCS)](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/assets/manage/asset-microservices-configure-and-use)
- [Granite Workflow API JavaDoc](https://developer.adobe.com/experience-manager/reference-materials/6-5/javadoc/com/adobe/granite/workflow/package-summary.html)
