Skip to content
AI & Agents
Skill

/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

From plugin
adobe-skills
162160 skills6 agents4 MCP
Install
$ npx -y skills add adobe/skills --skill workfront-ui-extension --agent claude-code

How 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.md
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 / 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 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
Ships withadobe-skills

Repository of Adobe skills for AI coding agents.

Get the whole plugin

Other skills on adobe-skills.