Cookbook

Structural Patterns

Structural patterns provide the foundational architecture that every extension uses to bootstrap, render surfaces, manage navigation, and handle loading states. Understanding these patterns ensures your extension integrates smoothly with the host application.

Bootstrap — Entry Point

Set up your extension entry point in src/index.tsx. The createExtension factory bootstraps the sandboxed runtime and registers all surfaces with the framework.

import { createExtension } from '@stackable-labs/sdk-extension-react'
import { Header } from './surfaces/Header'
import { Content } from './surfaces/Content'
import { Footer } from './surfaces/Footer'

const Extension = () => (
  <>
    <Header />
    <Content />
    <Footer />
  </>
)

// NOTE: extensionId is optional — used when connected to a registered extension
createExtension(() => <Extension />, { extensionId: 'my-extension' })

Surfaces — Declaring UI Slots

Each surface renders into a specific layout slot in the embedding application. The id prop must match a target declared in your manifest.json.

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

export function Header() {
  return (
    <Surface id="slot.header">
      <ui.Text className="text-sm font-medium">Header content</ui.Text>
    </Surface>
  )
}

Content

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

export function Content() {
  return (
    <Surface id="slot.content">
      <ui.Card>
        <ui.CardContent>
          <ui.Text>Extension content</ui.Text>
        </ui.CardContent>
      </ui.Card>
    </Surface>
  )
}
import { Surface, ui } from '@stackable-labs/sdk-extension-react'

export function Footer() {
  return (
    <Surface id="slot.footer">
      <ui.Text className="text-xs">Powered by My Extension</ui.Text>
    </Surface>
  )
}
import { Surface, ui } from '@stackable-labs/sdk-extension-react'

export function FooterLinks() {
  return (
    <Surface id="slot.footer-links">
      <ui.FooterLink href="https://example.com">My Extension</ui.FooterLink>
    </Surface>
  )
}

Store — Cross-Surface State & Navigation

Extensions use createStore for shared state across surfaces. The store replaces traditional URL routing — there are no URLs inside the sandbox. Use discriminated unions for view state to get exhaustive type checking.

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

// store.ts
type ViewState = { type: 'list' } | { type: 'detail'; id: string }

interface AppState {
  viewState: ViewState
}

export const appStore = createStore<AppState>({
  viewState: { type: 'list' },
})

// Content.tsx
export function Content(): React.ReactElement {
  const viewState = useStore(appStore, (s) => s.viewState)

  const goToDetail = (id: string) => appStore.set({ viewState: { type: 'detail', id } })
  const goBack = () => appStore.set({ viewState: { type: 'list' } })

  return (
    <Surface id="slot.content">
      {(viewState.type === 'list') && (
        <ui.Button onClick={() => goToDetail('abc123')}>View Detail</ui.Button>
      )}
      {(viewState.type === 'detail') && (
        <ui.Stack direction="column" gap="2">
          <ui.Text>Viewing: {viewState.id}</ui.Text>
          <ui.Button onClick={goBack}>Back</ui.Button>
        </ui.Stack>
      )}
    </Surface>
  )
}

Loading States & Guards

Handle loading states and missing context gracefully. Always show a skeleton while context loads, and guard against missing data before rendering.

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

export function Content(): React.ReactElement {
  const { loading, customerId } = useContextData()

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

  if (!customerId) {
    return (
      <Surface id="slot.content">
        <ui.Stack direction="column" gap="1" className="p-4 items-center">
          <ui.Icon name="user" size="sm" />
          <ui.Text className="text-xs text-muted-foreground">No customer selected</ui.Text>
        </ui.Stack>
      </Surface>
    )
  }

  return (
    <Surface id="slot.content">
      <ui.Text className="text-sm">Customer: {customerId}</ui.Text>
    </Surface>
  )
}

Surface Context

Read host-pushed context for a specific surface using the lower-level useSurfaceContext hook.

import { useSurfaceContext, Surface, ui } from '@stackable-labs/sdk-extension-react'
import type { ContextData } from '@stackable-labs/sdk-extension-contracts'

export function Header(): React.ReactElement {
  // Lower-level hook — reads host-pushed context for this specific surface
  const context = useSurfaceContext() as ContextData

  return (
    <Surface id="slot.header">
      <ui.Text className="text-xs">{context.customerId ?? 'No customer'}</ui.Text>
    </Surface>
  )
}

Auto-generated from Stackable Extension SDK. Questions/Issues? developers@stackablelabs.com

Previous
Recipes
Structural Patterns | Stackable Labs :. Dev Documentation