Create a project for UI Extensibility
To create a custom UI extension, you must create an App Builder project for it.
This page describes how to generate a generic App Builder project with the aio command line. “Generic” means the project does not start from a product-specific template. Starting with a generic app keeps the project simple and allows it to connect with Workfront Fusion.
It may be useful to familiarize yourself with the following concepts and terminology regarding creating a project for use with Adobe Fusion AI Extensibility.
-
The Adobe Developer Console (https://developer.adobe.com/console) is the web dashboard where your project lives.
-
Terminology:
table 0-row-2 1-row-2 2-row-2 3-row-2 4-row-2 Term What it means Organization Your company’s Adobe org. The same org you use in Fusion. Project A container for one app/extension. You will create one project for your extension. Workspace A copy of the project’s configuration for a stage of work. Every project has a Production workspace, and you typically also use a Stage workspace for testing. Think of workspaces like “environments.” Credentials / Services Permissions your app is allowed to use. The defaults created for you are enough to start. -
There are two ways to create a project:
- Automatic (recommended): The command
aio app initcreates the project and workspaces for you while generating the code. This article describes this process. - Manual: You create the project yourself in the Developer Console first, then point
aioat it. We recommend doing this only if your organization requires projects to be created centrally.
- Automatic (recommended): The command
-
When deciding which workspace to use, develop and deploy to Stage first. Fusion loads a Stage build only when the user turns on Stage testing in their Fusion profile (user avatar menu > Product Settings > Fusion Profile > Preferences > Stage extensions); otherwise, only published Production extensions appear. You can also preview locally with
aio app run, then promote to Production later.For more information on promoting to production, see Publish your extension.
Run aio app init
-
Open a terminal.
-
In the terminal, move to the folder where you keep projects.
-
Run:
code language-sh aio app init my-fusion-extension --standalone-appmy-fusion-extensionis the folder/app name. You can select this name, but use lowercase letters, hyphens, and no spaces.--standalone-apptells the CLI to create a plain application skeleton instead of asking you to pick a product template. This is the key to avoiding the AEM (or any other) template.
-
When prompted, select your Organization (if you belong to more than one).
-
When prompted, select Create new project and accept the suggested name, or pick an existing empty project.
The command sets up the Stage and Production workspaces automatically.
The command also generates files into the
my-fusion-extensionfolder and runsnpm install. -
Continue to Confirm project creation.
aio app init my-fusion-extension > (without --standalone-app). When it asks “What templates do you want to search for?” or shows a checklist of templates, do not select a product template like AEM. Choose the option to create a standalone application / “All extension points → none”.Check project creation
-
In the terminal, move into the created folder:
code language-sh cd my-fusion-extensionYou should see a structure similar to this (some files omitted):
code language-none my-fusion-extension/ |--- app.config.yaml // main configuration (you will edit this) |--- package.json //dependencies and scripts |--- src/ // your source code |--- web-src/ or src/.../web-src/ // front-end files (HTML/JS)The two files you care about most are:
app.config.yaml: The central configuration. Later in the process you will add anextensions:section here that connects your app to a Fusion extension point.package.json: Lists the libraries your app uses. You will add the Adobe UI Extensibility guest library here.
-
Continue to Add required libraries.
Add required libraries
Your extension needs two libraries:
@adobe/uix-guest: Lets your app talk to Fusion (the host).@adobe/react-spectrum: Adobe’s React UI components, so your screen matches Adobe’s look and feel. (Optional, but recommended; you can use plain HTML instead.)
To install these libraries:
-
In the terminal, run:
code language-sh npm install @adobe/uix-guest @adobe/react-spectrum -
(Conditional) If your generated project does not already include React, also install it:
code language-sh npm install react react-dom react-router-dom -
Continue to Confirm the project builds.
Confirm the project builds
Before changing anything, make sure the empty project builds
-
In the terminal, run:
code language-sh aio app buildIf this completes without errors, your tools and project are correctly configured. You are ready to connect the project to Fusion.
note tip TIP If the build fails, the most common cause is an unsupported Node.js version. Run node --versionand make sure it is 18 or 20.- For information on installing Node.js, see Set up your tools.
- For information on other possible errors, see Troubleshooting.
-
Continue to Configure the project for Fusion.
Configure the project for Fusion
The next step in setting up your custom extension is to connect your generic project to Workfront Fusion.
You will:
- Create a folder for your extension
- Tell App Builder about a Fusion extension point (in
app.config.yaml). - Describe your extension’s pieces (in
ext.config.yaml). - Register your widget so Fusion knows its title and where its UI lives.
We use fusion/nav-organization/1 throughout. To target the Team section instead, swap in fusion/nav-team/1 everywhere. To support both, repeat the pattern for each.
Create a folder for your extension
-
Create your files so the project looks like this:
code language-none my-fusion-extension/ |-- app.config.yaml |-- src/ |-- fusion-nav-organization-1/ // one folder per extension point |-- ext.config.yaml |-- web-src/ |-- src/ |-- components/ |-- App.js |-- ExtensionRegistration.js |-- DashboardWidget.js |-- Constants.jsWe recommend naming the folder after the extension point (
fusion-nav-organization-1). The exact name is up to you, but it must match what you reference inapp.config.yaml. -
Continue to Declare the extension point in
app.config.yaml.
Declare the extension point in app.config.yaml
-
Open
app.config.yamland update its contents to:code language-yaml extensions: fusion/nav-organization/1: $include: src/fusion-nav-organization-1/ext.config.yamlThese contents describe the following:
extensions:: This app implements one or more extension points.fusion/nav-organization/1: The Fusion slot you are plugging into. The name must match exactly, including version1.$include:: This points to a second config file (created in the next step) that describes this extension’s contents. Keeping it in a separate file keepsapp.config.yamlclean and lets you add more extension points later.
note NOTE If you are targeting both extensions, list both, each with its own folder: code language-yaml extensions: fusion/nav-organization/1: $include: src/fusion-nav-organization-1/ext.config.yaml fusion/nav-team/1: $include: src/fusion-nav-team-1/ext.config.yaml- Continue to Describe the extension in
ext.config.yaml
Describe the extension in ext.config.yaml
-
Create
src/fusion-nav-organization-1/ext.config.yamlwith:code language-yaml operations: view: - type: web impl: index.html web: web-src hooks: pre-app-build: node node_modules/@adobe/uix-guest/scripts/generate-metadata.js pre-app-run: node node_modules/@adobe/uix-guest/scripts/generate-metadata.jsThese contents describe the following:
operations.view: Declares that your extension provides a view (a visible UI), served fromindex.html. This is what makes your extension show a screen rather than run only in the background.web: web-src: The folder that holds your front-end files. App Builder builds everything under here and hosts it on Adobe’s Content Delivery Network (CDN).hooks: Small commands that run automatically at build/run time. Thegenerate-metadata.jsscript ships with@adobe/uix-guest, and produces anapp-metadata.jsonfile that your registration code needs (see Step 4). You do not write this script; you just reference it.
note NOTE If you also need server-side logic too, you can also add serverless actions(small backend functions). Actions are optional and not required to render a UI, so we leave them out to keep this guide focused. If you add them later, declare anactions:folder here and aruntimeManifest:inapp.config.yaml. The most common reason to add one is to call Workfront/Fusion APIs without hitting browser CORS.
For information on calling APIs, see Calling Workfront and Fusion APIs. -
Continue to Set a stable extension ID.
Set a stable extension ID
Your extension requires a unique id that both frames share.
For information on frames in relation to custom extensions, see Frames included in a UI Extension.
-
Create
src/fusion-nav-organization-1/web-src/src/components/Constants.js:code language-js module.exports = { extensionId: 'my-fusion-extension' };Use the same value everywhere your code refers to the extension id.
-
Continue to Register your widget.
Register your widget
“Registration” is how the hidden background frame tells Fusion what your extension offers. You declare a dashboard.getWidget() method that returns your widget’s title and the URL of its visible UI.
-
Create
src/fusion-nav-organization-1/web-src/src/components/ExtensionRegistration.js.
The important part is theregister(...)call:code language-js import { register } from "@adobe/uix-guest"; import metadata from "../../../../app-metadata.json"; import { extensionId } from "./Constants"; async function init() { await register({ id: extensionId, metadata, methods: { dashboard: { getWidget() { return { id: extensionId, title: "My Fusion tool", // shown on the Fusion nav button description: "What this tool does", url: "/index.html#/my-widget", // route to your visible UI hideWidgetHeader: false // false = Fusion shows the title }; } } } }); } init().catch(console.error);Key points:
titleis the label Fusion puts on the navigation button. IfhideWidgetHeaderisfalse, Fusion also shows the title as a header above your UI.urlis the route to your visible UI inside this same app. Here it is a hash route (#/my-widget) handled by your front-end router (set up on the next page). It must resolve to the component that renders your screen.metadatacomes fromapp-metadata.json, which thegenerate-metadatahook creates for you at build time. Import it as shown.- The
dashboard.getWidgetmethod name is the agreed contract Fusion calls to discover your widget. Keep thedashboardnamespace andgetWidgetname.
The backend of your extension is now complete. The next step into build the extension’s UI.
For instructions on building the UI, see Build the custom extension UI.