Complete reference for Pika's tag definition system that enables AI-driven custom UI components.
Overview
Section titled “Overview”The widget system allows LLMs to generate custom UI elements by including special tags in their responses. Each tag is defined by a TagDefinition that specifies:
- Widget type and rendering
- When and where it can be used
- Instructions for the LLM
- Access control
Import Statements
Section titled “Import Statements”Tag Definition Types
Section titled “Tag Definition Types”import type { TagDefinition, TagDefinitionLite, TagDefinitionWidget, TagInstructionForLlm, WidgetRenderingContexts} from 'pika-shared/types/chatbot/chatbot-types';Widget and Metadata Types
Section titled “Widget and Metadata Types”import type { WidgetAction, WidgetMetadata, WidgetMetadataState, SpotlightWidgetDefinition, WidgetCallbackContext} from 'pika-shared/types/chatbot/webcomp-types';Tag Definition Types
Section titled “Tag Definition Types”TagDefinition
Section titled “TagDefinition”Complete tag definition with all configuration.
interface TagDefinition<T extends TagDefinitionWidget> { tag: string; scope: string; usageMode: 'global' | 'chat-app'; widget: T; llmInstructionsMd?: string; status: 'enabled' | 'disabled' | 'retired'; renderingContexts: WidgetRenderingContexts; canBeGeneratedByLlm: boolean; canBeGeneratedByTool: boolean; description: string; dontCacheThis?: boolean; createdBy: string; lastUpdatedBy: string; createDate: string; lastUpdate: string;}Fields:
tag- Tag name (e.g.,'chart','order-status')scope- Namespace to prevent collisions (e.g.,'pika','custom')usageMode-'global'(auto-available) or'chat-app'(explicit enable)widget- Widget configuration (type and settings)llmInstructionsMd- Markdown instructions injected into promptsstatus- Lifecycle staterenderingContexts- Where widget can be renderedcanBeGeneratedByLlm- Whether LLM can use this tagcanBeGeneratedByTool- Whether tools can generate this tagdescription- Admin-facing description
TagDefinitionLite
Section titled “TagDefinitionLite”Minimal tag identifier.
interface TagDefinitionLite { scope: string; tag: string;}Used for referencing tags without full definition.
Widget Types
Section titled “Widget Types”Four types of widgets are supported:
type TagDefinitionWidget = | BuiltInWidget | CustomCompiledInWidget | WebComponentWidget | PassThroughWidget;BuiltInWidget
Section titled “BuiltInWidget”Pika-provided widgets (chart, image, prompt).
interface BuiltInWidget { type: 'built-in'; builtInType: 'chart' | 'image' | 'prompt';}CustomCompiledInWidget
Section titled “CustomCompiledInWidget”Svelte components compiled into your app.
interface CustomCompiledInWidget { type: 'custom-compiled-in';}References renderers in customRenderers registry.
WebComponentWidget
Section titled “WebComponentWidget”Dynamically loaded web components.
interface WebComponentWidget { type: 'web-component'; webComponent: { s3Bucket: string; s3Key: string; encoding?: 'gzip' | 'none'; };}PassThroughWidget
Section titled “PassThroughWidget”Non-rendering semantic markup.
interface PassThroughWidget { type: 'pass-through';}Usage Modes
Section titled “Usage Modes”Global Tags
Section titled “Global Tags”Automatically available to all chat apps unless explicitly disabled.
const globalTag: TagDefinition<BuiltInWidget> = { tag: 'chart', scope: 'pika', usageMode: 'global', // Available everywhere by default status: 'enabled', widget: { type: 'built-in', builtInType: 'chart' }, // ... other fields};Disabling global tags per chat app:
// In chat app configurationfeatures: { tags: { enabled: true, tagsDisabled: [ { scope: 'pika', tag: 'chart' } // Disable charts in this app ] }}Chat-App Tags
Section titled “Chat-App Tags”Must be explicitly enabled per chat app.
const chatAppTag: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'acme', usageMode: 'chat-app', // Requires explicit enable status: 'enabled', widget: { type: 'web-component', webComponent: { s3Bucket: 'acme-widgets', s3Key: 'order-status.js', encoding: 'gzip' } }, // ... other fields};Enabling chat-app tags:
// In chat app configurationfeatures: { tags: { enabled: true, tagsEnabled: [ { scope: 'acme', tag: 'order-status' } ] }}Rendering Contexts
Section titled “Rendering Contexts”Control where widgets can be rendered.
WidgetRenderingContexts
Section titled “WidgetRenderingContexts”interface WidgetRenderingContexts { inline?: boolean; canvas?: boolean; dialog?: boolean; spotlight?: boolean;}Contexts:
inline- Within message text flowcanvas- Full-width dedicated areadialog- Modal/popup overlayspotlight- Featured prominent display
Example:
renderingContexts: { inline: true, // Can appear in text canvas: true, // Can appear full-width dialog: false, // Explicitly disabled for dialogs spotlight: false // Explicitly disabled for spotlight}LLM Instructions
Section titled “LLM Instructions”Guide the LLM on when and how to use tags.
TagInstructionForLlm
Section titled “TagInstructionForLlm”type TagInstructionForLlm = | { type: 'line'; text: string } | { type: 'block'; title: string; lines: Array<{ type: 'line'; text: string }>; };Example Instructions
Section titled “Example Instructions”llmInstructions: [ { type: 'line', text: 'Use the chart tag when you need to visualize numerical data' }, { type: 'block', title: 'Chart Types', lines: [ { type: 'line', text: '- bar: For comparing categories' }, { type: 'line', text: '- line: For showing trends over time' }, { type: 'line', text: '- pie: For showing proportions' } ] }, { type: 'line', text: 'Example: <chart type="bar" data=\'{"labels":["A","B"],"datasets":[{"data":[1,2]}]}\' />' }]Markdown Format
Section titled “Markdown Format”Instructions can also be provided as Markdown:
llmInstructionsMd: `# Chart Tag
Use the chart tag when you need to visualize numerical data.
## Chart Types- **bar**: For comparing categories- **line**: For showing trends over time- **pie**: For showing proportions
## Example\`\`\`xml<chart type="bar" data='{"labels":["A","B"],"datasets":[{"data":[1,2]}]}' />\`\`\``Lifecycle Management
Section titled “Lifecycle Management”Status Values
Section titled “Status Values”type TagStatus = 'enabled' | 'disabled' | 'retired';enabled- Active and availabledisabled- Temporarily hiddenretired- Permanently archived
Complete Examples
Section titled “Complete Examples”Built-In Chart Tag
Section titled “Built-In Chart Tag”const chartTag: TagDefinition<BuiltInWidget> = { tag: 'chart', scope: 'pika', usageMode: 'global', widget: { type: 'built-in', builtInType: 'chart' }, llmInstructionsMd: `Use for data visualization. Supports bar, line, and pie charts.`, status: 'enabled', renderingContexts: { inline: false, canvas: true, dialog: true, spotlight: true }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Renders interactive charts', createdBy: 'system', lastUpdatedBy: 'system', createDate: '2024-01-01T00:00:00Z', lastUpdate: '2024-01-01T00:00:00Z'};Custom Web Component Tag
Section titled “Custom Web Component Tag”const orderStatusTag: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'acme', usageMode: 'chat-app', widget: { type: 'web-component', webComponent: { s3Bucket: 'acme-components', s3Key: 'widgets/order-status-v1.js', encoding: 'gzip' } }, llmInstructionsMd: `# Order Status Widget
Display real-time order status with tracking information.
## Usage\`\`\`xml<order-status orderId="12345" />\`\`\`
## Attributes- **orderId** (required): The order ID to display`, status: 'enabled', renderingContexts: { inline: true, canvas: true, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Shows order status and tracking', dontCacheThis: false, createdBy: 'admin@acme.com', lastUpdatedBy: 'admin@acme.com', createDate: '2024-01-15T10:00:00Z', lastUpdate: '2024-01-15T10:00:00Z'};Custom Compiled-In Tag
Section titled “Custom Compiled-In Tag”const dataTableTag: TagDefinition<CustomCompiledInWidget> = { tag: 'data-table', scope: 'custom', usageMode: 'chat-app', widget: { type: 'custom-compiled-in' // References customRenderers['data-table'] }, llmInstructionsMd: `Display tabular data with headers and rows.
Example:\`\`\`xml<data-table>{ "headers": ["Name", "Age", "City"], "rows": [ ["John", "30", "NYC"], ["Jane", "25", "LA"] ]}</data-table>\`\`\``, status: 'enabled', renderingContexts: { inline: false, canvas: true, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Renders data tables', createdBy: 'admin', lastUpdatedBy: 'admin', createDate: '2024-01-15T10:00:00Z', lastUpdate: '2024-01-15T10:00:00Z'};Pass-Through Metadata Tag
Section titled “Pass-Through Metadata Tag”const metadataTag: TagDefinition<PassThroughWidget> = { tag: 'metadata', scope: 'system', usageMode: 'global', widget: { type: 'pass-through' }, llmInstructionsMd: 'Use for semantic markup that should not render visually', status: 'enabled', renderingContexts: { inline: true, canvas: false, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Semantic markup with no visual rendering', createdBy: 'system', lastUpdatedBy: 'system', createDate: '2024-01-01T00:00:00Z', lastUpdate: '2024-01-01T00:00:00Z'};Tag Management APIs
Section titled “Tag Management APIs”Admin APIs
Section titled “Admin APIs”Full access to tag definitions (requires admin permissions).
Create or Update:
POST /api/chat-admin/tagdef{ "tagDefinition": { /* TagDefinition object */ }, "userId": "admin-user"}Delete:
DELETE /api/chat-admin/tagdef{ "tagDefinition": { "tag": "my-tag", "scope": "custom" }, "userId": "admin-user"}Search (including disabled):
POST /api/chat-admin/tagdef/search{ "includeInstructions": true, "paginationToken": null}Chat APIs
Section titled “Chat APIs”Read-only access to enabled tags.
Search (enabled only):
POST /api/chat/tagdef/search{ "includeInstructions": false, "paginationToken": null}Web Component Development
Section titled “Web Component Development”Creating a Web Component
Section titled “Creating a Web Component”class OrderStatusWidget extends HTMLElement { connectedCallback() { const orderId = this.getAttribute('orderId'); this.render(orderId); }
async render(orderId) { const status = await this.fetchOrderStatus(orderId);
this.innerHTML = ` <div class="order-status"> <h3>Order #${orderId}</h3> <p>Status: ${status.state}</p> <p>Tracking: ${status.tracking}</p> </div> `; }
async fetchOrderStatus(orderId) { // Fetch from API return { state: 'Shipped', tracking: '1Z999...' }; }}
customElements.define('order-status-widget', OrderStatusWidget);Uploading to S3
Section titled “Uploading to S3”# Compress and uploadgzip order-status-widget.jsaws s3 cp order-status-widget.js.gz s3://my-bucket/widgets/ \ --content-encoding gzip \ --content-type application/javascriptRegistering the Tag
Section titled “Registering the Tag”const tagDef: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'custom', usageMode: 'chat-app', widget: { type: 'web-component', webComponent: { s3Bucket: 'my-bucket', s3Key: 'widgets/order-status-widget.js.gz', encoding: 'gzip' } }, // ... other fields};
// POST to /api/chat-admin/tagdefDynamic Spotlight Registration
Section titled “Dynamic Spotlight Registration”Components can register themselves in spotlight programmatically at runtime without creating database tag definitions.
manuallyRegisterSpotlightWidget()
Section titled “manuallyRegisterSpotlightWidget()”Register a web component as a spotlight widget from code.
manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): voidParameters:
interface SpotlightWidgetDefinition { /** Tag name (e.g., 'my-widget') */ tag: string;
/** Scope/namespace (e.g., 'my-app') */ scope: string;
/** Display title in UI */ tagTitle: string;
/** Custom element name (optional, inferred if not provided) */ customElementName?: string;
/** Widget sizing configuration (optional) */ sizing?: WidgetSizing;
/** Component agent instructions (optional) */ componentAgentInstructionsMd?: Record<string, string>;
/** * Auto-show widget immediately? * - true: Widget appears in spotlight automatically * - false: Widget registered but not shown until renderTag() called * @default true */ autoCreateInstance?: boolean;
/** * Display order in spotlight (lower = higher) * @default 0 */ displayOrder?: number;
/** * Only allow one instance? * @default true */ singleton?: boolean;
/** * Show in unpinned widget menu? * @default true */ showInUnpinnedMenu?: boolean;
/** * Optional initial metadata (title, actions, icon) to apply when rendered. * Metadata can be updated later via getWidgetMetadataAPI(). * @since 0.11.0 */ metadata?: WidgetMetadata;}Behavior:
- Registration is ephemeral - does not persist across page refreshes
- Component must re-register on each page load
- Merges with database-sourced tag definitions
- Respects user preferences (unpinning, ordering)
- Compatible with all spotlight features
Example:
import { getPikaContext } from 'pika-shared/util/wc-utils';
class MyWidget extends HTMLElement { async connectedCallback() { const context = await getPikaContext(this);
// Register in spotlight context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'my-widget', scope: 'acme', tagTitle: 'My Widget', customElementName: 'acme-my-widget', displayOrder: 0, autoCreateInstance: true }); }}Common Use Cases:
// Conditional registration (admin-only)if (context.appState.identity.isSiteAdmin) { context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'admin-tools', scope: 'internal', tagTitle: 'Admin Tools' });}
// Feature-gated widgetconst features = await getEnabledFeatures();if (features.includes('beta-analytics')) { context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'analytics', scope: 'acme', tagTitle: 'Analytics Dashboard', displayOrder: 5 });}
// Base widget for saved instancescontext.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'saved-chart', scope: 'acme', tagTitle: 'Saved Chart', autoCreateInstance: false, // Don't show by default singleton: false, // Allow multiple instances showInUnpinnedMenu: false // Hide from menu});Best Practices:
Widget Metadata and Actions
Section titled “Widget Metadata and Actions”WidgetMetadata Interface
Section titled “WidgetMetadata Interface”Widgets can dynamically update their chrome (title bar, actions, loading state):
interface WidgetMetadata { /** Widget title shown in chrome */ title: string;
/** * Optional Lucide icon name (fetched automatically and set as iconSvg). * Use snake-case: 'arrow-big-down' not 'arrowBigDown' */ lucideIconName?: string;
/** Optional icon SVG markup for the widget title */ iconSvg?: string;
/** Optional color for the widget icon (hex, rgb, or CSS color name) */ iconColor?: string;
/** Optional action buttons */ actions?: WidgetAction[];
/** Optional loading status */ loadingStatus?: { loading: boolean; loadingMsg?: string; };}WidgetAction Interface
Section titled “WidgetAction Interface”Action buttons that appear in widget chrome (spotlight, canvas, dialog):
interface WidgetAction { /** Unique identifier for this action */ id: string;
/** Tooltip/label for the action (also button text in dialog context) */ title: string;
/** SVG markup string for the icon */ iconSvg: string;
/** Whether action is currently disabled */ disabled?: boolean;
/** If true, renders as default/prominent button (dialog footer) */ primary?: boolean;
/** * Handler called when action is clicked. * Automatically receives widget context with element, instanceId, and full Pika context. * @since 0.11.0 - Callback now receives WidgetCallbackContext parameter */ callback: (context: WidgetCallbackContext) => void | Promise<void>;}WidgetCallbackContext Interface
Section titled “WidgetCallbackContext Interface”Context automatically provided to action button callbacks:
interface WidgetCallbackContext { /** The web component element */ element: HTMLElement;
/** The unique instance ID assigned to this component */ instanceId: string;
/** The full Pika context with app state, chat state, and more */ context: PikaWCContext;}Action Button Example
Section titled “Action Button Example”import { extractIconSvg } from 'pika-shared/util/icon-utils';
// Define an actionconst refreshAction: WidgetAction = { id: 'refresh', title: 'Refresh Data', iconSvg: await extractIconSvg('refresh-cw', 'lucide'), disabled: false, callback: async ({ element, instanceId, context }) => { // Access the widget element const widget = element as MyWidget;
// Show loading state const metadata = context.chatAppState.getWidgetMetadataAPI( 'my-app', 'my-widget', instanceId, context.renderingContext ); metadata.setLoadingStatus(true, 'Refreshing...');
try { // Refresh data await widget.refreshData(); context.appState.showToast('Data refreshed!', { type: 'success' }); } catch (error) { context.appState.showToast('Refresh failed', { type: 'error' }); } finally { metadata.setLoadingStatus(false); } }};
// Register metadata with actionconst metadata: WidgetMetadata = { title: 'My Widget', lucideIconName: 'chart-line', actions: [refreshAction]};
// Apply metadata when renderingawait context.chatAppState.renderTag('my-app.my-widget', 'canvas', { data }, metadata);Rendering Widgets with Metadata
Section titled “Rendering Widgets with Metadata”The renderTag() method now accepts optional metadata:
// Render with initial metadataawait chatAppState.renderTag( 'acme.dashboard', 'canvas', { userId: '123' }, { title: 'Sales Dashboard', lucideIconName: 'bar-chart', actions: [ { id: 'export', title: 'Export Data', iconSvg: await extractIconSvg('download', 'lucide'), callback: async ({ context }) => { // Export logic here } } ] });Metadata can be:
- Passed initially via
renderTag()(as shown above) - Updated dynamically via
getWidgetMetadataAPI().setMetadata() - Applied when manually registering spotlight widgets via
SpotlightWidgetDefinition.metadata
Auto-Generated Tag Definitions
Section titled “Auto-Generated Tag Definitions”When renderTag() is called with 'canvas' or 'dialog' context for a tag that doesn't exist, the system automatically generates a minimal tag definition:
// This works even if 'acme.custom-widget' isn't registered in the databaseawait chatAppState.renderTag('acme.custom-widget', 'canvas', { data });Auto-generated tags include:
- Minimal required fields (scope, tag, status)
- The requested rendering context enabled
usageMode: 'chat-app'status: 'enabled'
Best Practices
Section titled “Best Practices”Security Considerations
Section titled “Security Considerations”Troubleshooting
Section titled “Troubleshooting”Tag Not Rendering
Section titled “Tag Not Rendering”- Check tag is enabled:
status === 'enabled' - Verify usage mode and chat app configuration
- Check rendering context matches usage
- Ensure web component loaded successfully
LLM Not Using Tag
Section titled “LLM Not Using Tag”- Review
llmInstructionsMdclarity - Check
canBeGeneratedByLlm === true - Verify tag is in chat app's enabled list
- Test with explicit examples in prompt
Web Component Not Loading
Section titled “Web Component Not Loading”- Verify S3 bucket permissions
- Check S3 key path is correct
- Ensure CORS configured on bucket
- Verify encoding matches actual file
Related Documentation
Section titled “Related Documentation”- Widget Tag Guide - How-to guide for creating tags
- Web Components Guide - Building web components
- Custom Component Interface - Component API reference
- pika-ux Module - Pre-built UI components
- Chat App Configuration - Configuring tag features
Type Source Files
Section titled “Type Source Files”- chatbot-types.ts - Tag definition types
- webcomp-types.ts - Web component interfaces