@stackable-labs/cli-app-extension
v3.0.1
Published
CLI for scaffolding Stackable extension projects.
Readme
@stackable-labs/cli-app-extension
CLI for scaffolding new Stackable App Extension projects.
Quick Start
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest create my-extensionOr run interactively with no arguments:
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest createImportant: Always include
@latestto ensure you get the most recent version. Bothnpxandpnpm dlxaggressively cache packages.
Interactive Mode
When run without all required flags, the CLI guides you through a step-by-step prompt flow:
| Step | Prompt | Description |
|---|---|---|
| 1 | App | Select the App you are building an Extension for (fetched live from the API) |
| 2 | Name | Display name for your Extension (e.g. My Commerce Extension) |
| 3 | Template | Choose a template flavor: Minimal (hello-world), Starter (common patterns), or Kitchen Sink (every component/capability) |
| 4 | Targets | Multiselect from the Surface targets/slots exposed by the selected app (skipped for Minimal and Kitchen Sink) |
| 5 | Extension Port | Dev server port for the Extension (default: 6543) |
| 6 | Preview Port | Dev server port for the Preview host (default: 6544) |
| 7 | Directory | Output directory path (default: kebab-case of name) |
| 8 | Confirm | Review all selections (including template) before scaffolding |
Commands
create [name]
Create a new Extension project from a template.
Usage: stackable-app-extension create [name] [options]
Arguments:
name Extension project name (prompted if omitted)
Options:
--app-id <id> Skip App selection
--project-id <id> Studio project ID (fetches files/manifest from Studio)
--template <flavor> Template flavor: minimal, starter, kitchen-sink
--extension-port <port> Extension dev server port (default: 6543)
--preview-port <port> Preview dev server port
--skip-install Skip package manager install
--skip-git Skip git initializationscaffold [extensionId]
Scaffold a local project from an existing Extension registered in the platform.
Usage: stackable-app-extension scaffold [extensionId] [options]
Arguments:
extensionId Extension ID to scaffold from (prompted if omitted)
Options:
--app-id <id> App ID (auto-resolved from extensionId if omitted)
--project-id <id> Studio project ID (fetches files/manifest from Studio)
--template <flavor> Template flavor: minimal, starter, kitchen-sink
--extension-port <port> Extension dev server port (default: 6543)
--preview-port <port> Preview dev server port
--skip-install Skip package manager install
--skip-git Skip git initializationWhen extensionId is provided without --app-id, the CLI automatically resolves which App owns the extension by checking all your registered apps. When scaffolding a newly created extension (no existing local project), the template picker is shown unless --template or --project-id is provided.
update [extensionId]
Update an existing Extension's metadata (name, targets, bundle URL, version, enabled state).
Usage: stackable-app-extension update [extensionId] [options]
Arguments:
extensionId Extension ID to update (prompted if omitted)
Options:
--app-id <id> App ID (auto-resolved from extensionId if omitted)
--name <name> New Extension name
--targets <targets> Comma-separated target slots (validated against app)
--bundle-url <url> New bundle URL
--enabled <bool> Enable/disable Extension
--set-version <version> Explicit version (skips auto-compute)
--dir <path> Project root (default: cwd)When extensionId is provided without --app-id, the CLI automatically resolves which App owns the extension by checking all your registered apps. If --app-id is also provided, it takes precedence.
auth
Manage CLI authentication.
stackable-app-extension auth login # Authenticate via browser
stackable-app-extension auth logout # Clear stored credentials
stackable-app-extension auth status # Show current auth statusLocal Development
To run the CLI locally from source against the live API:
pnpm build
pnpm cliOr pass flags directly:
pnpm cli -- --skip-install --skip-gitScaffolded Project Structure
my-extension/
├── packages/
│ ├── extension/ ← Extension source (Vite + React)
│ │ ├── src/
│ │ │ ├── index.tsx ← Extension entry point
│ │ │ └── surfaces/ ← One component per selected target slot
│ │ ├── manifest.json ← Extension manifest (id, targets, permissions)
│ │ └── .env ← EXTENSION_PORT, PREVIEW_PORT
│ └── preview/ ← Preview host app (Vite + React)
│ └── src/
│ └── App.tsx ← Host app wiring up the extension
├── turbo.json
└── package.jsondev Command
Start local dev servers with a public Cloudflare tunnel for testing:
pnpm previewUsage: stackable-app-extension dev [options]
Options:
--dir <path> Project root (default: cwd)
--extension-port <port> Override Extension port
--preview-port <port> Override Preview port
--no-tunnel Skip tunnel, just run vite dev
-h, --help Display helpThe dev command:
- Reads
.env.stackablefor cached App/Extension context (prompts if missing) - Starts Cloudflare tunnels for both extension and preview servers
- Starts Vite dev servers with hot-reload
- Displays a Host App Query Param you can append to your host app URL to test against the real deployed host
Host App Override
The dashboard displays one or two copyable query params you can append to your host app URL to override the extension's bundleUrl for that browser session only. The @stackable-labs/embeddables SDK detects the param and loads from your tunnel instead of the production bundle. No DB changes, no shared state — each developer gets isolated overrides.
The CLI emits one of two formats depending on whether the dev session token request succeeded:
Blob format (default — when authenticated): the second half of <extensionId>:<value> is a base64url-encoded JSON {url, token} blob. The token is short-lived, signed, and verified by the host. Required for _stackable_staging mode.
?_stackable_dev=ext-123:eyJ1cmwiOiJodHRwczovL2FiYy50cnljbG91ZGZsYXJlLmNvbSIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXp...The dashboard also surfaces the staging variant when applicable:
?_stackable_staging=ext-123:eyJ1cmwiOiJodHRwczovL2FiYy50cnljbG91ZGZsYXJlLmNvbSIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXp...The _stackable_staging variant signals the host to apply staging-environment-specific handling and always requires the token — there is no legacy fallback. If you don't see a Staging Param in the dashboard, the dev session token request failed; check auth status and re-run.
Legacy format (fallback — when no token): if requestDevSessionToken fails (auth misconfigured, network error, etc.), the dashboard falls back to a token-less <extensionId>:<tunnelUrl> form for _stackable_dev only. The host's parser accepts this format too but cannot apply staging-mode overrides without the token.
?_stackable_dev=ext-123:https://abc.trycloudflare.comMultiple Extensions
To override multiple extensions at once, use either syntax. Both blob entries and legacy entries can be mixed in the same comma-joined string — the parser tries each entry independently.
Comma-separated (single param) — preferred:
?_stackable_dev=ext-123:eyJ1cmwi...,ext-456:eyJ1cmwi...Mixed blob + legacy in one string also works (e.g. when one extension authenticated and another didn't):
?_stackable_dev=ext-123:eyJ1cmwi...,ext-456:https://def.trycloudflare.comRepeated params:
?_stackable_dev=ext-123:eyJ1cmwi...&_stackable_dev=ext-456:eyJ1cmwi...The combined staging variant works the same way (blob-only, since staging requires the token):
?_stackable_staging=ext-123:eyJ1cmwi...,ext-456:eyJ1cmwi...The stackable-extensions repo's pnpm preview:all orchestrator (which spawns one cli-app-extension dev --headless per extension under --only / --except filters) emits both combined lines automatically in its summary footer — ?_stackable_dev=... always, ?_stackable_staging=... only when at least one session got a token.
Note: The
devcommand never updates thebundleUrlin the database. It is purely local.
Development Workflow
cd my-extension
pnpm install # if --skip-install was used
pnpm dev # starts both Extension + Preview with hot-reloadThe preview host runs at the configured preview port and hot-reloads whenever the Extension changes.
Troubleshooting
Stale CLI version after update
Both npx and pnpm dlx cache downloaded packages. If you're seeing an older version after a new release:
npx — Always use @latest and clear the cache if needed:
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest createpnpm dlx — Bypass the 24-hour dlx cache:
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest createNote: After a new version is published to npm, there may be a brief delay (up to ~15 minutes) before the npm CDN propagates the update.
Requirements
- Node 20+
- pnpm (recommended) or npm/yarn
