Greetings,
I would like to create conditional loop behavior in my AEM workflow. I have found two possible implementations in the documentation, but am having trouble getting either to work.
The first method would be to use the built in 'GoTo' step. However, I want the loop to execute conditionally based upon the value of a variable that must be calculated on the server. My idea was to generate this value in a custom workflow process immediately before the 'GoTo' step and then pass the value to the 'GoTo'. However, I have been unable to find a way to successfully pass the variable such that it will be visible in the 'GoTo' step's ECMA script. Please let me know if there is a way to do this.
The second method is to use the com.adobe.granite.workflow.exec.Route API. When attempting to use this however, the workflow steps do no execute in the order I expected.
Consider the following workflow diagram:
Under my test case I expect the order of execution to be:
1. Flow Start
2. Publishing Gatekeeper
3. References Exist
4. Or Split
5. Broken Links Email to Unpublish Initiator
6.Continue to Unpublish? (Participant Step: await user action)
7. Or Split
8. Reassign to Publish (initiate loop: goto 'Continue to Unpublish')
6. Continue to Unpublish?
7. Or Split
8. Reassign to Publish (now break out of loop and continue normally)
9. Email Authors About Broken Links
10. Or Split
11. Deactivate Page
The Routes API is used in the custom workflow process 'Reassign to Publish' to send the workflow back to the step 'Continue to Unpublish?' (which is a Participant Step). In the logs I can see that the Routes API does trigger the 'Continue to Unpublish?' workflow to execute again. However, what is odd is that the 'Email Authors About Broken Links' also executes as if the reroute never occurred. I would have expected it to wait until 'Reassign to Publish' executed for the second time (and breaks out of the loop). I have been sure to disable 'Handler Advance' on the 'Reassign to Publish' step.
Here is the source code for the 'Reassign to Publish' step. Please let me know if you have any thoughts. Thank you.
@component(
service=WorkflowProcess.class,
immediate = true,
property= {
"service.description=Reassign to publishers and return to 'Continue to Unpublish?' step",
"service.vendor=TNC",
"process.label=Reassign to Publishers and Retry"
})
public class ReassignToPublishers implements WorkflowProcess {
private static final Logger LOG = LoggerFactory.getLogger(ReassignToPublishers.class);
@reference
private TNCUtilities tncUtilities;
@Override
public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap metaDataMap)
throws WorkflowException {
String contentPath = workItem.getWorkflowData().getPayload().toString();
try (ResourceResolver resolver = tncUtilities.getWriteResourceResolver()) {
String initiator = workItem.getWorkflow().getInitiator();
String publisher = workItem.getWorkflow().getMetaDataMap().get("publisher", String.class);
LOG.info("Immediately before initiatorCanPublish call.");
LOG.info("PUBLISHER: {}, INITIATOR: {}", publisher, initiator);
if (publisher == null
/*&& !WorkflowHelper.initiatorCanPublish(initiator, contentPath, resolver)*/) {
LOG.info("Inside initiatorCanPublish()");
//UserManager userManager = resolver.adaptTo(UserManager.class);
publisher = "admin";
// WorkflowHelper.getPublisher(session, userManager, contentPath);
LOG.info("Desginate publisher {} as next assignee", publisher);
workItem.getWorkflow().getMetaDataMap().put("publisher", publisher);
LOG.info("GoTo 'Continue To Unpublish?' step.");
List<Route> routes = wfSession.getBackRoutes(workItem, false);
Route specificRoute = null;
for (Route route : routes) {
LOG.info("ROUTE NAME: {}", route.getName());
if (route.getName().equals("Continue to Unpublish?")) {
specificRoute = route;
break;
}
}
if (specificRoute != null) {
LOG.info("Warping to: {}", specificRoute.getName());
wfSession.complete(workItem, specificRoute);
}
} else {
String assignee = publisher != null ? publisher : initiator;
LOG.info("Assignee: {} has publish rights for content path: {}. No reassignment needed.",
assignee, contentPath);
}
} catch (Exception re) {
LOG.error("An error occurred while reassigning to publisher: {}", contentPath, re);
}
LOG.info("Exiting ReassignToPublishers workflow step.");
}
}
Solved! Go to Solution.
Views
Replies
Total Likes
Hello @johnagordon83
Thank you for testing this out
Could you try the below :
Use these available predefined variables in AEM ECMA workflow scripts:
workItem
workflowSession
workflowData
args (process arguments)
metaData (step metadata)
>> To access workflow-wide variables (MetaDataMap), use:
workflowData.getMetaDataMap().get("variableName");
>> Set or update variables in a custom process step, for example, in ECMA:
workflowData.getMetaDataMap().put("loopBack", true);
Hello @johnagordon83 ,
your step is advancing twice.
In your custom Process step (“Reassign to Publish”) you callwfSession.complete(workItem, specificRoute), and the step has Handler Advance = unchecked.
When Handler Advance is unchecked, AEM automatically advances the step on the default route after your execute() returns. Because you also call complete() manually, the engine:
reroutes back to “Continue to Unpublish?” (your complete()), and then
auto-advances along the original branch to “Email Authors About Broken Links”.
That’s why it looks like the reroute “didn’t stick”.
As a fix:
Open the “Reassign to Publish” step and check Handler Advance.
Keep your code that finds the back route and call exactly one wfSession.complete(workItem, specificRoute).
Do not return any additional complete() or let the engine auto-advance.
Alternatively:
If you prefer the built-in GoTo step and a server-calculated flag:
- In your process step (right before GoTo), set a workflow variable:
workItem.getWorkflowData().getMetaDataMap().put("loopBack", true);
- Then in the GoTo ECMA script:
var loop = workItem.getWorkflowData().getMetaDataMap().get("loopBack");
if (loop === true) {
route = "Continue to Unpublish?"; // the transition name to that step
} else {
route = "Next"; // whatever your forward route is called
}
- (Use the transition names as they appear in the model.)
That’s it, enable Handler Advance for manual routing, or don’t call complete() if you leave Handler Advance off.
ManviSharma,
Thank you for taking the time to respond to my question. However, I have attempted to implement your advice without success.
'workItem' isn't a variable that is defined in the 'GoTo' ECMA script. I have tried workflowData.getMetaDataMap(), but this map does not contain any variables that are defined in a custom workflow process using a command like: workItem.getWorkflowData().getMetaDataMap().put("loopBack", true);
Documentation indicates that enabling 'Handler Advance' enables automatic routing not manual routing: https://experienceleague.adobe.com/en/docs/experience-manager-65/content/implementing/developing/ext...
I will proceed with a solution that does not require a 'loop' mechanic. Thank you.
Views
Replies
Total Likes
Hello @johnagordon83
- To share variables between steps, use:
workItem.getWorkflow().getMetaDataMap()
NOT workItem.getWorkflowData().getMetaDataMap() (which is step-local).
- In ECMA script steps (like GoTo), access variables as:
workflow.metaDataMap["yourVariableName"]
- For conditional GoTo behavior:
Set the variable in your custom process step and check it in your GoTo ECMA script.
- If using Route API (wfSession.complete(workItem, specificRoute)):
> Disable "Handler Advance" in the step; otherwise all routes may auto-advance.
> Make sure you select the exact route name you want to loop to (case sensitive).
Reference :
https://experienceleague.adobe.com/en/docs/experience-manager-65/content/implementing/developing/ext...
Views
Replies
Total Likes
Thank you for your reply.
Unfortunately within the GoTo ECMA script the variable 'workflow' is not defined. Attempts to use it will result in an error similar to the following:
10.11.2025 13:51:14.084 *ERROR* [JobHandler: /var/workflow/instances/server0/2025-11-09/tnc-deactivation-unpublishing-workflow_356:/content/tnc/nature/us/en-us/test-pages/general-content-test] com.adobe.granite.workflow.core.rule.RuleEngineAdminImpl Cannot evaluate rule:
function check() {
log.error("Hello!");
//String publisher = workflow.metaDataMap["publisher"];
log.error(workflow.metaDataMap);
log.error("Publisher is: " + publisher);
return publisher != null;
}
com.adobe.granite.workflow.WorkflowException: org.apache.sling.api.scripting.ScriptEvaluationException: Failure running script /libs/workflow/scripts/dynamic.ecma: ReferenceError: "workflow" is not defined. (NO_SCRIPT_NAME#4)
at com.adobe.granite.workflow.core.rule.ScriptingRuleEngine.evaluate(ScriptingRuleEngine.java:117) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at com.adobe.granite.workflow.core.rule.RuleEngineAdminImpl.evaluate(RuleEngineAdminImpl.java:53) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at com.adobe.granite.workflow.core.WorkflowSessionImpl.evaluate(WorkflowSessionImpl.java:1620) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at com.adobe.granite.workflow.core.process.GotoProcess.execute(GotoProcess.java:65) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at com.adobe.granite.workflow.core.job.HandlerBase.executeProcess(HandlerBase.java:198) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at com.adobe.granite.workflow.core.job.JobHandler.process(JobHandler.java:271) [com.adobe.granite.workflow.core:2.0.240.CQ680-B0017]
at org.apache.sling.event.impl.jobs.JobConsumerManager$JobConsumerWrapper.process(JobConsumerManager.java:502) [org.apache.sling.event:4.2.24]
at org.apache.sling.event.impl.jobs.queues.JobQueueImpl.startJob(JobQueueImpl.java:351) [org.apache.sling.event:4.2.24]
at org.apache.sling.event.impl.jobs.queues.JobQueueImpl.access$100(JobQueueImpl.java:60) [org.apache.sling.event:4.2.24]
at org.apache.sling.event.impl.jobs.queues.JobQueueImpl$1.run(JobQueueImpl.java:287) [org.apache.sling.event:4.2.24]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Views
Replies
Total Likes
Hello @johnagordon83
Thank you for testing this out
Could you try the below :
Use these available predefined variables in AEM ECMA workflow scripts:
workItem
workflowSession
workflowData
args (process arguments)
metaData (step metadata)
>> To access workflow-wide variables (MetaDataMap), use:
workflowData.getMetaDataMap().get("variableName");
>> Set or update variables in a custom process step, for example, in ECMA:
workflowData.getMetaDataMap().put("loopBack", true);
Views
Likes
Replies
Views
Likes
Replies