Launching Workflows from the AEM Content Fragment Editor with UI Extensibility
Over the years, I’ve built a lot of AEM front-end customizations. These solutions leveraged node overlays, dialog tweaks, Coral UI and Granite UI to get the job done. They worked well but rarely felt future-proof. Customizations were loosely tied to product versions, which meant upgrades could break things. Adobe’s UI Extensibility framework changes that. It introduces a cleaner, more isolated model that works well with AEM as a Cloud Service and takes advantage of modern industry standards.
This post walks through how I used the framework to add a workflow launcher to the Content Fragment Editor. It lets authors select one or more fragments and kick off a workflow from a modal inside the editor.
The Goal
Add a button that lets an author:
-
Select one or more content fragments from the Content Fragment Editor
-
Launch a modal to choose from a filtered list of workflow models (retrieved via the AEM Workflows API)
-
Review the selected content fragments and optionally deselect any before proceeding
-
Start the workflow for each selected fragment with one click (batch start via the AEM Workflows API)
These small UI tweaks might not seem like a big deal—but they add up. Launching a workflow directly from the fragment editor saves authors from jumping between screens or copying paths manually. That reduces context switching and increases content velocity. Extensions like this are low-effort for developers, but they make AEM feel more responsive to how teams actually work.
Demo
Here's a short video showing the extensino in action - from selecting content fragments to launching a workflow in-context:
Prerequisites
This post assumes you’re familiar with basic AEM authoring and have access to an AEM as a Cloud Service environment. To follow along, you’ll also need a working Adobe Developer Console project and some experience with Adobe App Builder.
If you’re new to App Builder, start with the App Builder development flow guide to learn how to deploy an extension, and see how to preview an extension locally for testing your changes before deployment.
Solution Overview
This solution includes four main parts:
Front-end:
- ExtensionRegistration.js - Registers the “Workflows” button in the Content Fragment Editor. When clicked, it launches a modal and passes the selected content fragment paths via the URL.
- LaunchCFExtensionModal.js - Renders the modal UI. It parses the fragment paths from the URL, fetches available workflow models, and lets authors select a model and confirm which fragments to include.
Backend (Runtime Actions):
- Workflow Models - Queries AEM’s /var/workflow/models.json endpoint and returns a list of available workflows. This action handles token-based authentication and filters the results server-side.
- Workflow Instances - Receives a selected workflow and fragment paths, then starts the workflow for each path by posting to AEM’s /var/workflow/instances endpoint.
Note: Below is a simplified view of the extension code. The full source code including error handling, formatted display labels, loading states, and API calls is available on GitHub.
Adding the Button
I used the actionBar extension point, which allows you to add custom buttons to the Content Fragment Editor’s toolbar (see documentation). When the user selects one or more fragments, they’ll see a new “Workflows” button. The button launches a modal and passes the selected fragment paths via the URL.
// ExtensionRegistration.js
const guestConnection = await register({
id: extensionId,
methods: {
actionBar: {
getButtons() {
return [
{
id: 'launch-cf-extension',
label: 'Workflows',
icon: 'PublishCheck',
async onClick(selections) {
const contentFragmentPaths = selections.map(selection => selection.id);
const contentFragmentData = encodeURIComponent(JSON.stringify(contentFragmentPaths));
const modalURL = "/index.html#" + generatePath(
"/content-fragment/:fragmentId/launch-cf-extension-modal",
{ fragmentId: encodeURIComponent(selections[0].id) }
) + `?contentFragments=${contentFragmentData}`;
guestConnection.host.modal.showUrl({
title: "Workflows",
url: modalURL,
});
},
},
];
},
},
},
});
Note: This snippet registers a custom “Workflows” button in the Content Fragment Editor’s action bar. When clicked, it gathers the selected content fragments, encodes their paths, and opens a modal. The modal receives the fragment data via URL query parameters, enabling a clean handoff from the extension point to the UI layer.
Building the Modal
In the modal, we parse the fragment paths from the URL and fetch available workflow models from AEM. The user selects a model, reviews the selected fragments, and submits the workflow.
// LaunchCFExtensionModal.js
useEffect(() => {
(async () => {
const guestConnection = await attach({ id: extensionId });
setGuestConnection(guestConnection);
const auth = guestConnection.sharedContext.get('auth');
const searchParams = new URLSearchParams(window.location.hash.split('?')[1]);
const fragmentsParam = searchParams.get('contentFragments');
if (fragmentsParam) {
const paths = JSON.parse(decodeURIComponent(fragmentsParam));
setContentFragmentPaths(paths);
setSelectedFragments(new Set(paths));
}
const headers = {
'Authorization': `Bearer ${auth.imsToken}`,
'x-gw-ims-org-id': auth.imsOrg
};
const workflowEndpoint = config['aem-cf-console-admin-1/workflow-models'];
const workflowResponse = await actionWebInvoke(workflowEndpoint, headers, {});
const allowedWorkflowModels = [
"/var/workflow/models/delete_fragments",
"/var/workflow/models/move_fragments",
"/var/workflow/models/rename_fragments",
"/var/workflow/models/wcm-translation/create_language_copy",
"/var/workflow/models/wcm-translation/translate-language-copy"
];
const filteredWorkflows = workflowResponse.filter(wf =>
allowedWorkflowModels.includes(wf.uri)
);
setWorkflows(filteredWorkflows);
setIsLoading(false);
})();
}, []);
Starting the Workflow
Note: In this example, we use an Adobe I/O Runtime action to make the workflow API call. This approach avoids updating the AEM instance’s CORS configuration by proxying requests through App Builder. However, the cleaner long-term solution — especially for production — is to make the AEM call directly from the extension and configure AEM to accept requests from your App Builder domain. This reduces complexity and latency by removing the extra hop through Runtime.
In the workflow-instances/index.js runtime action, we loop through the provided fragment paths and start the selected workflow in AEM:
for (const path of params.contentFragmentPaths) {
const formData = new FormData();
formData.append('model', params.workflowModel);
formData.append('payloadType', 'JCR_PATH');
formData.append('payload', path);
formData.append('workflowTitle', `Workflow for ${path.split('/').pop()}`);
await fetch(`${params.AEM_HOST}/var/workflow/instances`, {
method: 'POST',
body: formData,
headers: {
Authorization: `Bearer ${token}`,
'x-gw-ims-org-id': orgId
}
});
}
Note: This runtime action receives a selected workflow model and a list of content fragment paths, then loops through each path to start a workflow in AEM. This approach supports batch triggering and cleanly separates client-side logic from the AEM API integration. The AEM Workflow API expects to receive the request body as formData.
Wrapping Up
This was a small addition, but it made a big difference. The integration feels natural and gives authors something useful right where they need it.
For me, it confirmed that Adobe’s UI Extensibility model is heading in the right direction. It’s modern, clean, and stable across releases.
Looking forward to building more with it.