Reference
Hooks & API
Learn how to build extensions with React hooks and APIs for interacting with the host platform, managing state across surfaces, and responding to events.
All imports from @stackable-labs/sdk-extension-react.
createExtension(factory, options?)
Bootstrap the extension runtime. Call once in src/index.tsx.
factory: () => React.ReactElement— render function returning all surfacesoptions?: { extensionId?: string }— extension identifier
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' })
Surface component
Wraps content for a target slot. The id must match a target in manifest.json.
<Surface id="slot.content">...</Surface>
useCapabilities()
Returns the capabilities object for calling host-mediated APIs.
const capabilities = useCapabilities()
// capabilities.context.read() — includes identity state in response
// capabilities.data.query(payload)
// capabilities.data.fetch(url, init?)
// capabilities.actions.toast(payload)
// capabilities.actions.invoke(action, payload?) — actions: newConversation, setConversationTags, setConversationFields, open, close, show, hide
// capabilities.extend.identity(payload) — enrich identity claims (prefer useExtendIdentity hook)
useStore(store, selector?)
Subscribe to a shared store. Re-renders when the selected state changes.
const viewState = useStore(appStore, (s) => s.viewState)
useContextData()
Reads host-provided context including extension settings. Returns { loading, customerId, customerEmail, messaging, settings, ... }. messaging.conversationId is the active Messaging conversation ID (or null until one exists).
const { loading, customerId, customerEmail, messaging, settings } = useContextData()
const conversationId = messaging?.conversationId
useSettings()
Convenience hook for reading extension settings. Returns non-secret settings scoped to this extension on this instance.
const settings = useSettings()
const apiBaseUrl = settings.baseUrl as string
useSurfaceContext()
Returns host-provided context specific to the current surface slot.
const surfaceContext = useSurfaceContext()
useExtension()
Returns extension-level context.
const { extensionId } = useExtension()
createStore(initialState)
Create a shared store for cross-surface state coordination.
const appStore = createStore<AppState>({ viewState: { type: 'menu' } })
Store<T> interface
get(): T— read current stateset(partial: Partial<T>): void— merge partial state updatesubscribe(listener: (state: T) => void): () => void— subscribe, returns unsubscribe fn
useIdentityEvent(eventType, handler)
Subscribe to identity events pushed from the host via the framework. Requires events:identity permission and matching entries in manifest events array.
eventType: 'login' | 'logout' | 'refresh' | 'expired'handler: (event: IdentityEvent) => voidIdentityEvent: { eventName: IdentityEventType, data: { state: IdentityState, timestamp: string } }IdentityState: { authenticated: boolean, user: UserIdentity | null, expiresAt?: string }
useIdentityEvent('login', (event) => {
console.log('User logged in:', event.data.state.user?.email)
})
useIdentityEvent('logout', () => {
console.log('User logged out')
})
useMessagingEvent(eventType, handler)
Subscribe to messaging events (e.g. postback button clicks) pushed from the host widget. Requires events:messaging permission and matching entries in manifest events array.
eventType: 'postback' | 'postback:<actionName>'handler: MessagingEventHandler—(event: MessagingEvent) => voidMessagingPostbackEvent: { eventName: 'postback', data: { actionName: string, conversationId: string, timestamp: string } }'postback'receives ALL postback events (requires elevated marketplace review)'postback:<actionName>'receives only events matching the specific actionName
useMessagingEvent('postback:Buy Now', (event) => {
console.log('Postback:', event.data.actionName, event.data.conversationId)
})
With useCallback (for memoized handlers):
import { useCallback } from 'react'
import { useMessagingEvent } from '@stackable-labs/sdk-extension-react'
import type { MessagingEventHandler } from '@stackable-labs/sdk-extension-contracts'
const handlePostback = useCallback<MessagingEventHandler>((event) => {
console.log('Postback:', event.data.actionName, event.data.conversationId)
}, [])
useMessagingEvent('postback:Buy Now', handlePostback)
useActivityEvent(eventType, handler)
Subscribe to activity events pushed from the host via the framework. Requires events:activity permission and matching entries in manifest events array.
eventType: 'click' | 'page_view' | 'form_submit' | 'product_view' | 'add_to_cart' | 'purchase' | 'search' | '*'(domain-stripped)handler: ActivityEventHandler—(event: ActivityEvent) => voidActivityEvent: { eventName: string, data: Record<string, unknown> }'*'receives ALL activity events
useActivityEvent('product_view', (event) => {
console.log('Activity:', event.eventName, event.data)
})
useEvent(eventType, handler)
Generic cross-domain event hook (should not be used unless absolutely required). Subscribe to any event using fully-qualified event types.
eventType: EventType— fully-qualified (e.g.,'activity:product_view','identity:login','messaging:postback')- Domain wildcard (e.g.,
'activity') receives all events in that domain handler: (event: BaseEvent) => void
useEvent('activity', (event) => {
console.log('Activity:', event.data)
})
useExtendIdentity(handler)
Register a handler to enrich identity JWT claims before signing. Requires extend:identity permission.
handler: ExtendIdentityHandler—(claims: IdentityBaseClaims) => Record<string, unknown> | Promise<Record<string, unknown>>IdentityBaseClaims: { external_id: string, email?: string, name?: string, [key: string]: unknown }
useExtendIdentity((claims) => ({
external_id: `custom_${claims.external_id}`,
loyalty_tier: 'gold',
}))
With useCallback (for memoized handlers):
import { useCallback } from 'react'
import { useExtendIdentity } from '@stackable-labs/sdk-extension-react'
import type { ExtendIdentityHandler } from '@stackable-labs/sdk-extension-contracts'
const handleExtend = useCallback<ExtendIdentityHandler>((claims) => ({
external_id: `custom_${claims.external_id}`,
loyalty_tier: 'gold',
}), [])
useExtendIdentity(handleExtend)
Identity via context.read()
Identity state is available in the context.read() response as an identity field. Requires context:read permission (no separate identity permission needed).
const context = await capabilities.context.read()
// context.identity — { authenticated, user, expiresAt? }