Workfront Ui Extension
Use when building or editing the React/Spectrum front-end SPA of a Workfront App Builder extension.
by adobe · Apache-2.0
The skill
Use when building or editing the React/Spectrum front-end SPA of a Workfront App Builder extension. Reach for this whenever the user is: registering or changing extension points in `ExtensionRegistration` — a Main Menu button, a left-panel (`secondaryNav`) item for a specific Workfront object type (Project, Task, Issue, Portfolio, Program), or a custom-form widget with specific height and width; adding a new route in `App.js` to match an extension point URL; reading the Workfront shared context to get the current user, `objCode`, `objID`, or `hostname`; calling a Runtime action from the SPA via `actionWebInvoke`; or debugging a widget or route that renders blank after being registered. Never call Workfront or Adobe APIs directly from the SPA — all API calls belong in a Runtime action (see `workfront-actions`).
Workfront UI extension (front end)
Part of the appbuilder-workfront family. This is the front end — the app screens the user sees (the "SPA"). It registers extension points (the spots where the app appears in Workfront) and calls Runtime actions (the cloud back end) for all data.
This skill is the Workfront-specific front end (extension points, WF shared context). For generic React/Spectrum patterns (pages, forms, data tables, dialogs, navigation) and ExC Shell / AEM UI surfaces, use
appbuilder-ui-scaffolder. A ready-to-edit registration example is inassets/ExtensionRegistration.example.js.
Rules
- One
register()call wires everything up; individual views useattach(). - Auth comes from
sharedContext+getWFInstanceUrl()— Workfront supplies the signed-in user and instance; don't build a login. - Talk to the back end with
actionWebInvokeonly — the browser must never call Workfront directly (/attask/api/…). Back-end code lives inworkfront-actions. - Every action replies with
{ data, error }— always checkerrorbefore usingdata.
Extension points (in ExtensionRegistration)
register() takes id at the top level (a non-empty slug identifying the extension); the extension points go inside methods. Each item's url must map to a route in App.js, and every id must be unique.
const guestConnection = await register({
id: extensionId, // top-level id, non-empty
metadata, // from app-metadata.json (generated by a build hook)
methods: {
id: extensionId, // ⚠️ REQUIRED here too — the menu item won't render without it (see Gotchas)
mainMenu: {
getItems() { return [{ id, url: '/index.html#/route', label, icon }] }
},
secondaryNav: { // left panel, per object type
PROJECT: { getItems() { return [{ id, label, icon, url: '/route' }] } },
// register each separately: PROJECT, TASK, ISSUE, PORTFOLIO, PROGRAM
},
widgets: { // embed in a custom-form field
getItems() {
return [{
id, url: '/index.html#/widgets1', label,
dimensions: { height, width, maxHeight, maxWidth } // all optional
}]
}
},
}
})
Widget id/url/label are required; dimensions is optional.
Routing (App.js)
Add a <Route> per extension-point url:
<Route exact path="custom-application" element={<CustomApplication />} />
Shared context
sharedContext is get-only (.get(key) — not iterable). Confirmed shape from the Workfront host:
const ctx = conn?.sharedContext
const auth = ctx?.get('auth') // { imsClientId, imsOrgID, imsToken }
const user = ctx?.get('user') // { ID, email }
const host = ctx?.get('hostname') // e.g. ai-dev-arm.devtest.workfront-dev.adobe.com (no protocol)
// also: protocol; plus objCode, objID, isLoginAs, isInBulkEditing on object-scoped points
const imsToken = auth?.imsToken
const imsOrgId = auth?.imsOrgID // ⚠️ key is `imsOrgID` (capital ID) — NOT imsOrgId / imsOrg
Everything an action needs is right here — pass imsToken, imsOrgId (auth.imsOrgID), and host into actionWebInvoke. Don't call Workfront for the org: it's in auth, and a cross-origin currentUser fetch from the SPA is CORS-blocked anyway. Only set the x-gw-ims-org-id header when you have a value — Fetch turns undefined into the string "undefined" (→ 401 Org Id undefined). Widgets receive the same context.
Workers / errors
Do heavy CSV/XLSX (spreadsheet) work in Web Workers — background threads, so the screen doesn't freeze — but never call the WF API inside a worker. Show a toast (small popup notice) on failure; never expose tokens.
Gotchas (from building a real Main Menu extension)
- Main Menu item not rendering? It's the
id, and the fix is trivial. The WF template scaffoldsregister({ metadata, methods: { id: extensionId, mainMenu } })withextensionId = ''inConstants.js. Two things are required for the item to appear: (1) giveextensionIda non-empty value inConstants.js, and (2) keepid: extensionIdundermethods— that placement is exactly what the menu needs; if you remove it, the item still registers (Workfront even callsgetItems) but silently never renders. Simplest working form: setextensionId, and keepid: extensionIdinsidemethods(having it at the top level of the config too is fine). Don't be thrown off by reading@adobe/uix-guest:register(config)doesnew GuestServer(config.id)thenguest.register(config.methods, …)and just forwardsmethodsto the host — so from the guest source a baremethods.idlooks like an ignored no-op. It isn't: Workfront's host side consumesmethods.id, and that behavior isn't visible in the guest package. Verified in a live Main Menu extension — the item does not render without it. (This is easy to misdiagnose as an environment problem, or as dead code — it's neither.) aio app init -y(or skipping the "Add a custom button to Main Menu Item" prompt) generates no menu item at all — nomainMenublock, no view component, noApp.jsroute, noicons.js, emptyextensionId. Either answer that prompt during init, or hand-add:icons.js(exportingicon1/icon2), themainMenublock, a<Route>, and the view component.- Calling actions: the template's
web-src/src/utils.jsexportsactionWebInvoke(url, headers, params, options = { method: 'POST' }). Action URLs are injected intoweb-src/src/config.jsonat build time under both"<action>"and"<package>/<action>"—import actions from '../config.json'and look up by name; never hardcode a Runtime URL. registervsattach: the background registration frame usesregister()(a GuestServer, declares the extension points); a displayed view usesattach()(a GuestUI). Both exposesharedContext.- The deployed app's URL is the Experience Cloud shell link — not the bare CDN. Clicking the Main Menu button navigates to
…/workfront/custom-applications/<extensionId>/<menuRoute>(org- and instance-scoped); that is the app's shareable URL, and afteraio app deployyou should hand it to the user (build recipe inworkfront-local-testing). First segment = the registrationid; second = the menu item's#/route. Two traps: reusing the app id as the second segment (…/<extensionId>/<extensionId>) loads the background registration frame, not the view; and the raw CDN…/index.html#/routerenders with no host → nosharedContext. If the item registers but won't render live, suspect the Workfront environment, not the code (seeworkfront-local-testing). - React Spectrum
TableView's "select all" header checkbox gives you the string'all', not aSet.onSelectionChangeforselectionMode="multiple"normally hands you aSetof row keys, and code likeselectedKeys.size > 0works fine when the user checks rows one at a time. But clicking the header's own "select all" checkbox setsselectedKeysto the literal string'all'instead —'all'.sizeisundefined, so a naiveselectedKeys.size > 0check silently evaluates tofalseand a bulk-action button stays disabled with no visible error. Always branch on the string case:const count = selectedKeys === 'all' ? totalItems : selectedKeys.size, and use the same branch anywhere you turn the selection into an array of ids (selectedKeys === 'all' ? items.map(i => i.id) : Array.from(selectedKeys)). This is easy to miss in testing if you only ever click individual row checkboxes.
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: adobe/workfront-ui-extension-81820466778.
Details
- Publisher
- adobe
- Category
- Software Development › Frontend & UI
- License
- Apache-2.0
- Updated
- 2026-10-17
References
More from adobe
- Aa Conversion Funnel Analysis Analyzes a multi-step conversion funnel to find where visitors drop off and which steps have the…
- Aa Executive Briefing Generates a concise, executive-ready performance summary covering key metrics, trends, and what's…
- Aa Kpi Pulse Produces a compact KPI digest showing how key metrics changed over a period and what's driving th…
- Aa Segment Performance Comparator Compares the performance of two or more audience segments across key metrics side by side.