Get your first Stackable extension running locally in minutes, then deploy it to the marketplace. This guide walks you through scaffolding, development, and submission using the CLI.

## Choosing your path

Stackable supports two authoring paths. Both produce the same kind of extension and ship to the same marketplace.

| | AI Extension Studio | CLI (this guide) |
|---|---|---|
| **Best for** | Prototyping, learning, quick iterations | Production extensions, team workflows |
| **Code structure** | Single file | Multi-file (surfaces/, components/, lib/) |
| **Preview** | Built-in live preview | Local dev server + Cloudflare tunnel |
| **AI assistance** | Sidekick chat + smart insertion | Skills, MCP server, and Claude Code plugin (see [AI-Accelerated Development](/docs/reference/ai-accelerated-development)) |
| **Version control** | Auto-saved to cloud | Git-based |
| **Deployment** | Link to extension + deploy | CLI deploy command |

Both produce the same output — a Stackable extension that runs inside the embedding application via the same Remote DOM pipeline. **This guide covers the [CLI](/docs/reference/cli-reference) path.** For Studio, see [AI Extension Studio](/docs/reference/extension-studio).

## Prerequisites

- **Node.js** 22 or later
- **pnpm** (recommended) or npm

## 1. Create a New Extension

Scaffold a new extension project:

```bash
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest create my-extension
cd my-extension
pnpm install
```

This creates a project with:
- A working extension with header, content, and footer surfaces
- A local preview host for development
- TypeScript configuration
- A manifest with default permissions and targets

## 2. Preview Your Extension

Run the Stackable CLI dev server from your project directory:

```bash
pnpm --config.dlx-cache-max-age=0 dlx @stackable-labs/cli-app-extension@latest dev
```

> **Note:** This uses `pnpm dlx` to run the Stackable CLI without installing it
> globally. The `--config.dlx-cache-max-age=0` flag ensures you always get the
> latest version.

The dev command:
1. Starts a local Vite dev server for your extension with hot reload
2. Creates a public Cloudflare tunnel to your local server
3. Displays a **query parameter** you can use to preview your extension

### Testing against your extension

The CLI outputs a query param like:

```
?_stackable_dev=ext-123:eyJ1cmwiOiJodHRwczovL2FiYy50cnljbG91ZGZsYXJlLmNvbSIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXp...
```

The value after the colon is a `base64url`-encoded JSON `{url, token}` blob (the
default, when the CLI obtained a dev session token) or a plain URL (legacy
fallback when no token was available).

Copy the full param and **append it to the host site's URL** (the site or product
where your extension is installed/authorized) to load your local extension instead
of the production bundle. For example:

```
https://your-host-site.com/dashboard?_stackable_dev=ext-123:eyJ1cmwi...
```

This override is **browser-session only** — no database changes, no shared state.
Each developer gets isolated overrides. Changes you make locally appear immediately
in your extension via hot reload.

Use `--no-tunnel` if you only want to run the local Vite dev server without a tunnel.

## 3. Explore the Project

```
my-extension/
  packages/
    extension/
      public/
        manifest.json       # Targets, permissions, allowed domains
      src/
        index.tsx           # Entry point — createExtension bootstraps the runtime
        store.ts            # Shared state across surfaces
        surfaces/           # One component per surface slot
          Header.tsx
          Content.tsx
          Footer.tsx
        components/         # Feature components used by surfaces
        lib/                # API wrappers and utilities
    preview/                # Local preview host — DO NOT MODIFY
```

## 4. Make Your First Change

Open `packages/extension/src/surfaces/Content.tsx` and modify the content:

```tsx
import { Surface, ui } from '@stackable-labs/sdk-extension-react'

export const Content = () => (
  <Surface id="slot.content">
    <ui.Card>
      <ui.CardContent>
        <ui.Heading level={3}>Hello, Stackable!</ui.Heading>
        <ui.Text>My first extension is working.</ui.Text>
      </ui.CardContent>
    </ui.Card>
  </Surface>
)
```

Save the file — the preview updates automatically.

## 5. Add a Capability

To read host context data (customer info, etc.):

1. Add the permission to `manifest.json`:
```json
{
  "permissions": ["context:read"]
}
```

2. Use it in a surface:
```tsx
import { Surface, useContextData, ui } from '@stackable-labs/sdk-extension-react'

export const Content = () => {
  const { loading, customerId } = useContextData()

  if (loading) {
    return (
      <Surface id="slot.content">
        <ui.Skeleton />
      </Surface>
    )
  }

  return (
    <Surface id="slot.content">
      <ui.Card>
        <ui.CardContent>
          <ui.Text>Customer: {customerId}</ui.Text>
        </ui.CardContent>
      </ui.Card>
    </Surface>
  )
}
```

## 6. Submit for review

When your extension is ready, open it in the [Stackable admin dashboard](https://admin.stackablelabs.com), fill in the marketplace listing (icon, screenshots, description, tagline, support links), and submit for review. Most reviews complete within a few business days.

> *CLI `validate` and `deploy` commands are on the roadmap (see [CLI Reference](/docs/reference/cli-reference#validate-coming-soon)). Today, manifests are validated server-side during submission and via the `validate_manifest` MCP tool.*

## Next Steps

- **[Components](/docs/reference/components)** — browse the full UI component catalog
- **[Capabilities](/docs/reference/capabilities)** — learn about data.query, data.fetch, and more
- **[Surfaces](/docs/reference/surfaces)** — understand surface types and layout slots
- **[Patterns](/docs/reference/patterns)** — see common extension code patterns
