/workfront-ui-extension
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
$ npx -y skills add adobe/skills --skill workfront-ui-extension --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/workfront-ui-extension
Context preview
The summary Claude sees to decide when to auto-load this 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
SKILL.md
workfront-ui-extension.SKILL.mdname: workfront-ui-extension
description: "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`)."
license: Apache-2.0
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 in `assets/ExtensionRegistration.example.js`.
Rules
- One `register()` call wires everything up; individual views use `attach()`.
- Auth comes from `sharedContext` + `getWFInstanceUrl()` — Workfront supplies the signed-in user and instance; don't build a login.
- Talk to the back end with `actionWebInvoke` only — the browser must **never** call Workfront directly (`/attask/api/…`). Back-end code lives in `workfront-actions`.
- Every action replies with `{ data, error }` — always check `error` before using `data`.
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 / imsOrgEverything 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 scaffolds `register({ metadata, methods: { id: extensionId, mainMenu } })` with `extensionId = ''` in `Constants.js`. Two things are required for the item to appear: (1) give `extensionId` a **non-empty** value in `Constants.js`, and (2) **keep `id: extensionId` under `methods`** — that placement is exactly what the menu needs; if you remove it, the item still registers (Workfront even calls `getItems`) but **silently never renders**. Simplest working form: set `extensionId`, and keep `id: extensionId` inside `methods` (having it at the top level of the config too is fine). **Don't be thrown off by reading `@adobe/uix-guest`:** `register(config)` does `new GuestServer(config.id)` then `guest.register(config.methods, …)` and just *forwards* `methods` to the host — so from the guest source a bare `methods.id` looks like an ignored no-op. It isn't: **Workfront's host side consumes `methods.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 skippi
Read more
name: workfront-ui-extension description: "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`)." license: Apache-2.0
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 in `assets/ExtensionRegistration.example.js`.
Rules
- One `register()` call wires everything up; individual views use `attach()`.
- Auth comes from `sharedContext` + `getWFInstanceUrl()` — Workfront supplies the signed-in user and instance; don't build a login.
- Talk to the back end with `actionWebInvoke` only — the browser must **never** call Workfront directly (`/attask/api/…`). Back-end code lives in `workfront-actions`.
- Every action replies with `{ data, error }` — always check `error` before using `data`.
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 / imsOrgEverything 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 scaffolds `register({ metadata, methods: { id: extensionId, mainMenu } })` with `extensionId = ''` in `Constants.js`. Two things are required for the item to appear: (1) give `extensionId` a **non-empty** value in `Constants.js`, and (2) **keep `id: extensionId` under `methods`** — that placement is exactly what the menu needs; if you remove it, the item still registers (Workfront even calls `getItems`) but **silently never renders**. Simplest working form: set `extensionId`, and keep `id: extensionId` inside `methods` (having it at the top level of the config too is fine). **Don't be thrown off by reading `@adobe/uix-guest`:** `register(config)` does `new GuestServer(config.id)` then `guest.register(config.methods, …)` and just *forwards* `methods` to the host — so from the guest source a bare `methods.id` looks like an ignored no-op. It isn't: **Workfront's host side consumes `methods.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 skippi
Repo: adobe/skills
Other skills on adobe-skills.
- /aa-conversion-funnel-analysis
Analyzes a multi-step conversion funnel to find where visitors drop off and which steps have the worst leakage. Use this skill when someone describes a journey and asks about conversion rates, drop-off, fallout, or step completion. Trigger for "analyze our checkout funnel,"
Open skill - /aa-executive-briefing
Generates a concise, executive-ready performance summary covering key metrics, trends, and what's driving movement. Use this skill when someone needs to produce a briefing, executive summary, performance narrative, or stakeholder readout — for example, "write an exec summary of
Open skill - /aa-kpi-pulse
Produces a compact KPI digest showing how key metrics changed over a period and what's driving the movement. Use this skill when someone asks for a performance summary, a weekly recap, a morning briefing, a KPI update, or any variation of "how did we do this week/month." Also
Open skill - /aa-segment-performance-comparator
Compares the performance of two or more audience segments across key metrics side by side. Use this skill when someone wants to compare audiences or visitor groups — for example, "how do mobile visitors compare to desktop on conversion," "compare new vs. returning visitors,"
Open skill - /aa-top-movers-watchlist
Identifies which items (pages, campaigns, products, channels, regions) had the biggest increases or decreases for a key metric between two time periods. Use this skill when someone asks "what's up and what's down," "which campaigns moved the most," "top gainers and losers,"
Open skill - /cja-dimension-analysis
Comprehensive dimension analysis and reporting for CJA. Use this skill whenever the user wants to analyze one or more dimensions — including cardinality, distribution/skew, trends, anomalies, data quality errors, comparisons, and forecasting. Also trigger when someone asks "what
Open skill

