agentsclimarketplace

Genstudio extensibility scaffold

Skill Focus-GTS/firefly-services-skills/plugins/firefly-services/skills/genstudio-extensibility-scaffold

Scaffold a custom extension for Adobe GenStudio for Performance Marketing using App Builder — project setup, brand-config bindings, content-fragment integration, the extension manifest, the events the extension subscribes to, and the deploy/test loop. Use whenever the user mentions "GenStudio", "GenStudio extension", "GS4PM", "App Builder", "Adobe Experience Cloud extension", "extend GenStudio", "custom panel in GenStudio", "GenStudio plugin", or wants to integrate a custom workflow into the GenStudio experience. Encodes the early-mover pattern for the GenStudio ecosystem, including the bindings that are documented but rarely shown end-to-end.From its SKILL.md

Install
npx -y skills add Focus-GTS/firefly-services-skills --skill genstudio-extensibility-scaffold

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

16.3 KB, ~3.4k tokens by cl100k_base, as published. Nobody here has run it

GenStudio Extensibility Scaffold

The first-mover blueprint for extending Adobe GenStudio for Performance Marketing. GenStudio ships with a core experience; the App Builder extension layer is how a customer adapts it to their specific brand-governance, asset-routing, or campaign-orchestration workflow. The path exists, the pieces are documented in separate places, but no public reference stitches them together — until this skill.

This is a flag-plant: the extension pattern that customers will need over the next 18 months, written down before the rest of the ecosystem catches up.

When to Use This Skill

Use this skill when:

  • The customer has GenStudio for Performance Marketing and needs custom workflow integration
  • The user mentions "App Builder", "aio runtime", "Adobe Experience Cloud extension", "GenStudio extension"
  • A custom panel, action, or data binding is required inside GenStudio
  • Brand-config from an external system (PIM, DAM, brand registry) needs to flow into GenStudio
  • Generated assets need to route through a custom approval or syndication system

Do NOT use this skill when:

  • The customer is on Firefly Services API without GenStudio — that is the direct-API path; see firefly-services-bootstrap
  • The need is a one-time data export — use the Adobe Asset Hub APIs directly
  • The customer does not have App Builder entitlement — confirm SKU first

What "Extending GenStudio" Means

GenStudio for Performance Marketing is the Experience Cloud product that wraps Firefly Services + brand config + asset workflows + AEM Assets integration into a single marketer-facing UI. Extending it means adding capabilities that sit inside that UI without forking it.

Three extension shapes:

ShapeWhat it doesWhere it surfaces
UI extensionCustom panel, modal, or right-rail surfaceInside the GenStudio shell
Action extensionCustom action invoked from a context menu or buttonGenStudio toolbar / asset menu
Webhook extensionReacts to events (asset created, brand updated, campaign scheduled)Background — no UI

Most production extensions combine all three: a UI panel that surfaces external data, a custom action that triggers a workflow, and a webhook that reacts to GenStudio events.

Step 1 — Prerequisites

Before scaffolding:

# Install Adobe I/O CLI
npm install -g @adobe/aio-cli

# Verify version (need 9.x+)
aio --version

# Login to Adobe IMS
aio login

Confirm App Builder entitlement on the customer's IMS org:

aio console org list
aio console org select <org-name>
aio console project list

If aio console project list returns no projects with the App Builder template, the org does not have App Builder entitlement. Confirm with the customer's Adobe account team before continuing.

Step 2 — Scaffold the App Builder Project

# Create a working directory
mkdir genstudio-brand-extension && cd genstudio-brand-extension

# Bootstrap an App Builder project
aio app init genstudio-brand-extension

# When prompted:
#   - Organization: <customer's IMS org>
#   - Project: create new
#   - Workspace: Stage (start here; production added later)
#   - Component templates: select
#       [x] DX Experience Cloud SPA
#       [x] Action Generator
#       [x] Web Assets

The scaffold produces:

genstudio-brand-extension/
├── app.config.yaml              # Extension manifest — the most important file
├── package.json                 # Node deps; @adobe/aio-sdk pinned
├── src/
│   ├── dx-excshell-1/           # UI extension entry point
│   │   ├── web-src/             # React app
│   │   └── ext.config.yaml
│   └── actions/                 # Server-side actions (runtime)
│       ├── brand-sync/index.js
│       └── asset-route/index.js
├── test/                        # Unit + integration tests
└── e2e/                         # End-to-end against a real workspace

Step 3 — The Extension Manifest (app.config.yaml)

This file is the source of truth for what the extension does and where it surfaces. The shape:

extensions:
  dx/excshell/1:
    $include: src/dx-excshell-1/ext.config.yaml
    operations:
      view:
        - type: web
          impl: index.html
    actions:
      brand-sync:
        function: src/actions/brand-sync/index.js
        web: 'yes'
        runtime: nodejs:22
        inputs:
          LOG_LEVEL: debug
          BRAND_REGISTRY_URL: $BRAND_REGISTRY_URL
        annotations:
          require-adobe-auth: true
          final: true
      asset-route:
        function: src/actions/asset-route/index.js
        web: 'yes'
        runtime: nodejs:22
        annotations:
          require-adobe-auth: true
          final: true
    events:
      registrations:
        genstudio-brand-events:
          description: React to brand-config updates
          events_of_interest:
            - provider_metadata: dx_experience_events
              event_codes:
                - aem.assets.asset.metadata_updated
                - genstudio.brand.config.updated
          runtime_action: brand-sync

Key fields:

FieldPurpose
extensions['dx/excshell/1']Declares this is an Experience Shell UI extension
operations.viewThe web entry point — the React app that mounts inside GenStudio
actionsServer-side functions deployed to Adobe I/O Runtime
annotations.require-adobe-authForces every invocation through IMS auth (always true)
events.registrationsWhat Adobe events the extension subscribes to

Step 4 — Brand-Config Bindings

The most common reason to extend GenStudio is to bind external brand data into the experience. The pattern:

External brand registry (PIM / DAM / custom DB)
       ↓
GenStudio extension webhook (brand-sync action)
       ↓
GenStudio brand-config API (write the bindings)
       ↓
GenStudio UI surfaces the brand config when users generate

The brand-sync action runs server-side, reads from the external registry, and writes the brand parameters that GenStudio will pass to Firefly on generation.

Action skeleton:

// src/actions/brand-sync/index.js
const { Core } = require('@adobe/aio-sdk');
const fetch = require('node-fetch');

async function main(params) {
  const logger = Core.Logger('brand-sync', { level: params.LOG_LEVEL });
  try {
    const brandRegistryUrl = params.BRAND_REGISTRY_URL;
    const customerId = params.customerId;

    // 1. Pull latest brand config from external registry
    const externalConfig = await fetch(
      `${brandRegistryUrl}/customers/${customerId}/brand`,
      { headers: { 'Authorization': `Bearer ${params.REGISTRY_TOKEN}` } }
    ).then(r => r.json());

    // 2. Transform into GenStudio brand-config shape
    const genstudioBinding = {
      brandName: externalConfig.name,
      primaryColors: externalConfig.palette.primary,
      bannedTerms: externalConfig.denyList,
      approvedCustomModelIds: externalConfig.firefly.customModels,
      toneGuidelines: externalConfig.voice.guidelines,
    };

    // 3. Push to GenStudio
    const result = await fetch(
      `${params.GENSTUDIO_API_BASE}/brands/${customerId}/config`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${params.__ow_headers.authorization}`,
          'Content-Type': 'application/json',
          'x-api-key': params.GENSTUDIO_CLIENT_ID,
        },
        body: JSON.stringify(genstudioBinding),
      }
    );

    if (!result.ok) throw new Error(`GenStudio bind failed: ${result.status}`);

    logger.info('Brand config synced', { customerId });
    return { statusCode: 200, body: { synced: true, customerId } };
  } catch (err) {
    logger.error('Brand sync failed', err);
    return { statusCode: 500, body: { error: err.message } };
  }
}

exports.main = main;

Trigger this action on a webhook from the external registry, or on a schedule (aio app deploy with a cron annotation), or via the events registration in app.config.yaml.

Step 5 — UI Extension Surface

The UI extension is a React app that mounts inside the GenStudio shell. The Experience Shell SDK provides the integration:

// src/dx-excshell-1/web-src/src/App.jsx
import React, { useEffect, useState } from 'react';
import { Provider, defaultTheme } from '@adobe/react-spectrum';
import { register } from '@adobe/uix-guest';

export default function App() {
  const [guestConnection, setGuestConnection] = useState(null);
  const [campaignContext, setCampaignContext] = useState(null);

  useEffect(() => {
    (async () => {
      const conn = await register({
        id: 'genstudio-brand-extension',
        methods: {
          brandPanel: {
            getCustomBindings: async () => {
              const context = await conn.host.api.campaign.getCurrent();
              return loadBindingsForCampaign(context.id);
            },
          },
        },
      });
      setGuestConnection(conn);

      const ctx = await conn.host.api.campaign.getCurrent();
      setCampaignContext(ctx);
    })();
  }, []);

  return (
    <Provider theme={defaultTheme}>
      {campaignContext && (
        <BrandBindingsPanel campaign={campaignContext} connection={guestConnection} />
      )}
    </Provider>
  );
}

Key UIX patterns:

PatternPurpose
@adobe/uix-guest register()Connect to the GenStudio host; expose extension methods
conn.host.api.*Call back into GenStudio (get current campaign, list assets, etc.)
@adobe/react-spectrumRequired design system — extensions must use Spectrum for visual coherence

The Experience Shell host calls the extension's exposed methods when the user interacts with the extension's surface (panel open, action click). The extension can call back into the host to read campaign state, brand config, or asset metadata.

Step 6 — Local Development Loop

# Run locally, proxied into the staging workspace
aio app run

# This starts:
#   - Webpack dev server for the UI (https://localhost:9080)
#   - Local emulation of actions (via @adobe/aio-app-runtime)
#   - A registration of the local URL with the staging workspace

To test inside GenStudio:

  1. Open the customer's staging GenStudio tenant
  2. Navigate to a campaign that triggers the extension surface
  3. The extension loads from https://localhost:9080 (via the IMS-authed iframe)
  4. Edit, save, hot-reload

Common gotcha: the staging workspace has a CSP that forbids localhost by default. Add a development override in the workspace settings, or deploy to a dev-runtime URL instead.

Step 7 — Deployment

# Deploy to the staging workspace
aio app deploy

# Once verified, promote to production
aio app use --workspace Production
aio app deploy

Deployed extensions surface immediately in GenStudio for users with permissions in that workspace. There is no app-store review for first-party customer extensions — only Adobe Exchange listings require review.

ConcernPattern
Environment separationOne workspace per env (Stage, Prod); never share
Secrets$VAR references in app.config.yaml; values stored in .env (gitignored) and synced via aio app deploy --env
VersioningBump package.json version on each deploy; tag git for traceability
Rollbackaio app deploy --version <previous> to roll back to a prior tagged version

Step 8 — Subscribing to GenStudio Events

The events registration in app.config.yaml subscribes the extension to events fired by GenStudio and AEM Assets. Common events:

Event codeWhen it fires
genstudio.brand.config.updatedBrand config changed in the UI or via API
genstudio.campaign.scheduledA campaign was scheduled to publish
aem.assets.asset.createdAn asset was uploaded to AEM Assets
aem.assets.asset.metadata_updatedAsset metadata changed (tags, status, custom fields)
genstudio.generation.completedA Firefly generation triggered from GenStudio finished

Each event payload includes the IMS user context, the campaign / asset IDs, and timestamps. The runtime action receives the event, processes it, and (typically) writes back to the brand registry or asset store.

Validate

The extension is production-ready when:

  1. app.config.yaml declares all UI surfaces, actions, and event registrations explicitly
  2. All actions have require-adobe-auth: true annotation
  3. Secrets are referenced via $VAR, never inline
  4. The Spectrum design system is used throughout the UI
  5. The Stage workspace is the development target; Production deploys are tagged and rollback-able
  6. Event subscriptions are documented with what each handler does
  7. Local dev loop works without manual CSP overrides on Stage (use a dev-runtime URL)
  8. Brand-config bindings round-trip: external registry → action → GenStudio → reflected in generation

Troubleshooting & Edge Cases

  • Extension UI loads but methods return undefined: The register() call did not complete before the host called the method. Await register() fully, then expose the connection via state.
  • aio app deploy succeeds but the extension does not appear: Workspace permissions. The current user must have GenStudio admin permissions in that workspace to see new extensions. Check aio console workspace list-users.
  • Action returns 401 inside GenStudio: require-adobe-auth is true but the request is missing the IMS token. Confirm the UI is calling the action via the UIX guest API (which forwards auth), not via raw fetch.
  • Webpack dev server CSP errors: Stage workspace blocks localhost. Either add a dev CSP override, or deploy to a dev-runtime URL and use that as the source.
  • Event handler fires for every event instead of filtered ones: event_codes array is matched as a prefix list. Be specific — aem.assets.asset.created, not aem.assets.*.
  • Spectrum theme looks wrong inside the GenStudio shell: The host overrides theme in some surfaces. Pin to defaultTheme and accept the host scale; do not impose dark/light overrides.
  • Brand-sync action runs but bindings do not appear in GenStudio: API call succeeded but the customer's GenStudio tenant caches brand config for ~5 minutes. Wait or invalidate via the admin API.

Chaining with Other Skills

  • firefly-services-bootstrap — App Builder credentials follow the same console + OAuth pattern
  • firefly-services-auth — The token cache pattern applies to extension actions calling Firefly directly
  • firefly-brand-guardrails — The brand-config bindings populated here are what guardrails read
  • firefly-batch-pipeline — When the extension fires asset routing to a downstream pipeline
  • firefly-custom-models — The approvedCustomModelIds field in brand bindings points here

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most project setup skills give in ~3.4k tokens

Counted across 999 of the 1,637 authors here whose files we hold, read 2026-08-07

  • Ask one question at a timein 29 of 999, across 28 files
  • Detect the package manager from lockfilesin 28 of 999, across 9 files
  • Present findings to the userin 26 of 999, across 5 files
  • Explore current repo statein 24 of 999, across 3 files
  • Update the agent skills block in place if it existsin 24 of 999, across 3 files
  • Install husky lint-staged and prettierin 23 of 999, across 4 files
  • Create the lintstagedrc filein 22 of 999, across 3 files
  • Commit all changed filesin 22 of 999, across 3 files
  • Run lint-staged to verify it worksin 22 of 999, across 3 files
  • Create the husky pre-commit filein 21 of 999, across 2 files
  • Create a prettierrc file if missingin 21 of 999, across 2 files
  • Initialize huskyin 21 of 999, across 2 files

Said here and by no other author read

  • install the Adobe I/O CLI
  • verify App Builder entitlement exists
  • bootstrap an App Builder project
  • define the extension in the manifest
  • subscribe to GenStudio events in the manifest
  • require Adobe authentication for actions

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,871. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.