Step-by-step AEM Assets Content Hub Extension Development
The document helps you understand how to set up a local environment and start developing your first UI Extension for AEM Assets Content Hub.
About application
This example application uses the unified aem/assets/contenthub/1 extension point and demonstrates the three namespaces available in Content Hub:
assetDetailsadds a custom panel to the Asset Details dialog.cardadds actions to Asset Cards and Collection Cards.selectionBaradds actions for selected assets or collections.
It includes the routes and UI for the Asset Details panel. The sample Card and Selection Bar actions open dialogs to demonstrate one possible implementation, but extensions are not required to open a dialog. This guide focuses on a single Asset Details panel to keep the example concise.
Because it uses the unified extension point, the same extension can customize one or more of these surfaces by declaring the corresponding namespaces in the same register() call. You can keep only the namespaces and routes required by your extension.
More information about AEM Assets Content Hub extension points can be found at AEM Assets Content Hub Extension Points.
Create a project in Adobe Developer Console
UI Extensions, as with any App Builder application, are represented as projects in Adobe Developer Console.
data-slots=text
To begin, we need to create a new Project which will supply us with the configuration and resources.
- Sign in to Adobe Developer Console with your Adobe ID.
- Choose your account.
- Choose your profile or organization.
- Make sure you are in a proper organization (a switcher is in the right top corner).
- Click "Create new project" -> "Project from template":
And choose "App Builder":
- Fill in the project data.
Project Titleis used to identify your project within Adobe Developer Console and in CLI.App Namewill be used as a unique identifier for your application and this value cannot be changed after project creating.
After creating, you should see a new project generated with 2 default Workspaces. Each App Builder project has two default workspaces: Production and Stage. You can add more workspaces as needed. The Production workspace is special, as it is used for the submission and distribution flow.
Setting up local environment
- Node.js + npm (package manager). Make sure you are using the latest stable version of
Node.jsandnpm.
$ node -v
v20.9.0
$ npm -v
10.1.0
These are the current versions at the moment of creating the documentation. Make sure you are using the latest versions supported by Adobe I/O when you create the application.
If you already have Adobe I/O CLI on your local, please ensure you use the latest version of the library. You can check the version through:
aio -v
and compare it with
npm show @adobe/aio-cli
If your version is outdated, update your Adobe I/O CLI by running
npm install -g @adobe/aio-cli
More details are described in Local environment set up.
Initialize your extension using the AIO CLI and generate a base structure from the sample application
First, we need to sign in from CLI and bootstrap our project. Please complete all the steps described in Code Generation for the AEM Assets Content Hub Extension.
Also, we will not add an Adobe I/O action to our extension.
After generating the project, return to this guide for an overview of the generated components and instructions for testing the extension in Content Hub.
If you have worked with an Adobe App Builder App before, you will notice that you have generated a starter project of a UI Extension that implements extension points.
.
|-- README.md
|-- app.config.yaml
|-- extension-manifest.json
|-- hooks
| `-- post-deploy.js
|-- jest.setup.js
|-- package-lock.json
|-- package.json
`-- src
`-- aem-assets-contenthub-1
|-- actions
|-- e2e
|-- ext.config.yaml
|-- test
`-- web-src
|-- index.html
`-- src
|-- components
| |-- App.js
| |-- CardActionModal.js
| |-- Constants.js
| |-- ExtensionRegistration.js
| |-- PanelAssetDetailsExtensionTab.js
| `-- SelectionBarModal.js
|-- index.css
|-- index.js
`-- utils.js
# app.config.yaml
extensions:
aem/assets/contenthub/1:
$include: src/aem-assets-contenthub-1/ext.config.yaml
Overview of generated components
Routing
The root component src/aem-assets-contenthub-1/web-src/src/components/App.js contains the routing for the application. It defines the following routes:
- the first two are the default routes which trigger the
ExtensionRegistrationcomponent responsible for initial extension registration within the AEM Assets Content Hub application. - the
asset-details-extension-tabroute which invokes thePanelAssetDetailsExtensionTabcomponent responsible for rendering the custom panel content. This route gets called by the AEM Assets Content Hub application when the user clicks the custom panel icon. During the registration the extension will specify/#asset-details-extension-tabas content location. - the
card-action-modalroute which invokes theCardActionModalcomponent used by the sample Card action. This route gets called when the sample action opens its dialog. During the registration the extension will specify/#card-action-modalas content location. - the
selection-bar-modalroute which invokes theSelectionBarModalcomponent used by the sample Selection Bar action. This route gets called when the sample action opens its dialog. During the registration the extension will specify/#selection-bar-modalas content location.
React Router determines which part of the extension should be executed depending on the request.
import React from 'react';
import ErrorBoundary from 'react-error-boundary';
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
import ExtensionRegistration from './ExtensionRegistration';
import PanelAssetDetailsExtensionTab from './PanelAssetDetailsExtensionTab';
import CardActionModal from './CardActionModal';
import SelectionBarModal from './SelectionBarModal';
function App() {
return (
<Router>
<ErrorBoundary onError={onError} FallbackComponent={fallbackComponent}>
<Routes>
<Route index element={<ExtensionRegistration />} />
<Route exact path="index.html" element={<ExtensionRegistration />} />
<Route path="asset-details-extension-tab" element={<PanelAssetDetailsExtensionTab />} />
<Route path="card-action-modal" element={<CardActionModal />} />
<Route path="selection-bar-modal" element={<SelectionBarModal />} />
</Routes>
</ErrorBoundary>
</Router>
);
function onError(e, componentStack) {}
function fallbackComponent({ componentStack, error }) {
return (
<React.Fragment>
<h1 style={{ textAlign: 'center', marginTop: '20px' }}>
Extension rendering error
</h1>
<pre>{componentStack + '\n' + error.message}</pre>
</React.Fragment>
);
}
}
export default App;
Please note that your code may slightly differ from the given example depending on the version of the template for generation, but the main logic will be the same.
Keep only the routes required by your extension.
Extension registration
This logic component src/aem-assets-contenthub-1/web-src/src/components/ExtensionRegistration.js registers our extension with Content Hub as soon as it loads, so they can share data and communicate with each other.
import React from 'react';
import { Text } from '@adobe/react-spectrum';
import { register } from '@adobe/uix-guest';
import { extensionId } from './Constants';
const allowedRepos = ['delivery-p12345-e167890.adobeaemcloud.com'];
function getRepo() {
const search = new URLSearchParams(window.location.search);
return search.get('repo');
}
function shouldSkipRegistration(repo) {
return !allowedRepos.includes(repo);
}
function ExtensionRegistration() {
const repo = getRepo();
if (shouldSkipRegistration(repo)) {
return <Text>Skipped registration because the repository is not allowed.</Text>;
}
const init = async () => {
await register({
id: extensionId,
methods: {
assetDetails: {
getTabPanels() {
return [
{
id: 'asset-details-extension-tab',
tooltip: 'Asset Details Extension Tab',
icon: 'Extension',
title: 'Asset Details Extension Tab',
contentUrl: '/#asset-details-extension-tab'
}
];
}
}
}
});
};
init().catch(console.error);
return <Text>IFrame for integration with Host (Content Hub)...</Text>;
}
export default ExtensionRegistration;
We use the UIX SDK Guest library and call the register() function, which connects to the host application and declares methods the host can call on the extension.
The extension descriptor passed to the register() function in particular declares the getTabPanels() method, which in turn returns an array of custom panel descriptors upon a request from Content Hub.
In case of our extension, getTabPanels() returns a panel descriptor that defines the panel ID, tooltip, icon, title, and content location.
Custom panel
The src/aem-assets-contenthub-1/web-src/src/components/PanelAssetDetailsExtensionTab.js component is responsible for rendering the custom panel content.
import React, { useEffect, useState } from 'react';
import { attach } from '@adobe/uix-guest';
import {
Button,
ButtonGroup,
Flex,
Provider,
Text,
View,
defaultTheme
} from '@adobe/react-spectrum';
import { extensionId } from './Constants';
export default function PanelAssetDetailsExtensionTab() {
const [guestConnection, setGuestConnection] = useState();
useEffect(() => {
(async () => {
const connection = await attach({ id: extensionId });
setGuestConnection(connection);
})();
}, []);
function displayToast() {
guestConnection.host.toast.display({
variant: 'neutral',
message: 'Message from the Extension'
});
}
return (
<Provider theme={defaultTheme} height="100vh">
<View backgroundColor="gray-50" padding="size-300">
<Text>Custom Content Hub panel</Text>
<Flex justifyContent="center" marginTop="size-400">
<ButtonGroup>
<Button variant="primary" onPress={displayToast}>Click me!</Button>
</ButtonGroup>
</Flex>
</View>
</Provider>
);
}
The PanelAssetDetailsExtensionTab establishes a connection with AEM Assets Content Hub using the attach() function. The React Spectrum Provider applies the default theme to the panel content.
The displayToast() function displays a fixed toast message when the user clicks the button.
For more information about register(), attach(), and the common host APIs, see Common Concepts in Creating Extensions.
Test in local environment
From the extension project directory, run the following command:
aio app run
This will deploy the actions to Adobe I/O Runtime while running the UI part on the local machine.
➜ demo-extension-project % aio app run
create .vscode/launch.json
No change to package.json was detected. No package manager install will be executed.
To view your local application:
-> https://localhost:9080
To view your deployed application in the Experience Cloud shell:
-> https://experience.adobe.com/?devMode=true#/custom-apps/?localDevUrl=https://localhost:9080
press CTRL+C to terminate dev environment
Now your UI Extension is reachable via the URL displayed in the Terminal. You can test your UI Extension within Content Hub by adding the following URL parameters:
- ext (extension):
ext=https://localhost:9080 - devMode (development mode):
devMode=true - repo (Content Hub delivery repository):
repo=delivery-pxxx-eyyy.adobeaemcloud.com
Sample AEM Assets Content Hub URL: https://experience.adobe.com/?devMode=true&ext=https://localhost:9080&repo=delivery-p12345-e167890.adobeaemcloud.com#/assets/contenthub/
You may have to accept the self-signed certificate as described in the Troubleshooting section.
Run on Stage
After the development is completed, we can test our application on Stage before deploying to Production. For this we will use the Stage workspace in Adobe Developer Console.
First, make sure you are logged in to the proper organization and using the Stage workspace:
$ aio where
You are currently in:
1. Org: Alpha Org
2. Project: ContentHubExtension
3. Workspace: Stage
After that, we build and deploy the frontend files and assets:
aio app deploy
✔ Building web assets for 'aem/assets/contenthub/1'
✔ Deploying web assets for 'aem/assets/contenthub/1'
To view your deployed application:
-> https://123456-yournamespace-stage.adobeio-static.net/index.html
You can use the staging deployment URL (https://123456-yournamespace-stage.adobeio-static.net/index.html in this example) to test the extension with Content Hub by passing this URL to the ext= URL parameter.
https://experience.adobe.com/?devMode=true&ext=https://123456-yournamespace-stage.adobeio-static.net/index.html&repo=delivery-p12345-e167890.adobeaemcloud.com#/assets/contenthub/
To learn more about deployment, refer to the Deployment Overview.
Deploy on Production and create approval request
After the application has been completed, tested locally, and on Stage: we are ready to deploy it to Production. Refer to the UI Extensions Development Flow to learn how to do this.
Also in this document you can find the whole development flow of a UI Extensions.