Page Definitions
Configuring UI pages in plugins
Page Definitions
Pages are the primary way plugins provide user interfaces. Each page has its own route, state, and layout.
Page Structure
{
"pages": [
{
"route": "/my-plugin/page",
"title": "My Page",
"icon": "FileText",
"show_in_menu": true,
"state": { ... },
"sections": [ ... ],
"hooks": {
"on_mount": [ ... ],
"on_unmount": [ ... ]
}
}
]
} Required Fields
title
Display title shown in navigation.
"title": "Dashboard" Supports expressions:
"title": "{{state.userName}}'s Dashboard" route
URL path for the page.
"route": "/dashboard" Note: The plugin name is automatically prefixed, so /dashboard becomes /plugins/my-plugin/dashboard.
Route Patterns
| Pattern | Example | Description |
|---|---|---|
| Static | /dashboard | Fixed path |
| Nested | /settings/profile | Nested path |
| Parameter | /items/:id | Dynamic segment |
| Wildcard | /* | Catch-all |
Route Parameters
Access in expressions:
"route": "/items/:itemId",
"sections": [
{
"type": "Text",
"content": "Viewing item: {{params.itemId}}"
}
] sections
Array of root component schemas defining the UI.
"sections": [
{
"type": "Container",
"className": "p-6",
"children": [
{ "type": "Heading", "text": "Welcome", "level": 1 }
]
}
] See Components for all available components.
Optional Fields
icon
Icon displayed in navigation (from lucide-react).
"icon": "LayoutDashboard" Common icons:
Home,Settings,User,UsersFileText,FolderOpen,DatabaseChartBar,Activity,BellPlus,Edit,Trash,Search
Browse all at lucide.dev.
state
Page state definition.
"state": {
"items": {
"type": "array",
"default": []
},
"selectedItem": {
"type": "object",
"default": null,
"nullable": true
},
"isLoading": {
"type": "boolean",
"default": false
},
"searchQuery": {
"type": "string",
"default": ""
},
"count": {
"type": "number",
"default": 0
}
} State Field Types
| Type | Description | Default |
|---|---|---|
string | Text value | "" |
number | Numeric value | 0 |
boolean | True/false | false |
object | Key-value map | {} |
array | List of items | [] |
hooks
Page lifecycle hooks.
on_mount
Actions to execute when page loads.
"hooks": {
"on_mount": [
{
"type": "set_loading",
"loading": true
},
{
"type": "call_api",
"api": "my-plugin.getData",
"on_success": [
{ "type": "update_state", "path": "items", "from": "$response.data" },
{ "type": "set_loading", "loading": false }
],
"on_error": [
{ "type": "show_toast", "message": "Failed to load data", "level": "error" },
{ "type": "set_loading", "loading": false }
]
}
] Common patterns:
- Fetch initial data
- Initialize from URL parameters
- Set up subscriptions
on_unmount
Actions to execute when leaving the page.
"hooks": {
"on_unmount": [
{
"type": "update_state",
"path": "selectedItem",
"from": "null"
}
]
} Common patterns:
- Clear selections
- Cancel pending requests
- Save draft data
Layout Patterns
Simple Page
{
"sections": [
{
"type": "Container",
"className": "p-6 max-w-4xl mx-auto",
"children": [
{
"type": "PageHeader",
"title": "My Page",
"subtitle": "Page description here"
},
{
"type": "Card",
"content": {
"type": "Text",
"content": "Page content goes here"
}
}
]
}
]
} Dashboard Layout
{
"sections": [
{
"type": "Container",
"className": "p-6",
"children": [
{
"type": "PageHeader",
"title": "Dashboard",
"actions": [
{ "type": "Button", "label": "New Item", "icon": "Plus" }
]
},
{
"type": "Grid",
"columns": { "sm": 1, "md": 2, "lg": 4 },
"gap": "1rem",
"className": "mb-6",
"children": [
{ "type": "StatCard", "title": "Total Users", "value": "{{state.stats.users}}" },
{ "type": "StatCard", "title": "Active Sessions", "value": "{{state.stats.sessions}}" },
{ "type": "StatCard", "title": "Revenue", "value": "${{state.stats.revenue}}" },
{ "type": "StatCard", "title": "Growth", "value": "{{state.stats.growth}}%", "changeType": "increase" }
]
},
{
"type": "Grid",
"columns": { "sm": 1, "lg": 2 },
"gap": "1rem",
"children": [
{
"type": "Card",
"title": "Recent Activity",
"content": { "type": "List", "dataSource": "state:recentActivity" }
},
{
"type": "Card",
"title": "Performance",
"content": { "type": "Chart", "chartType": "line", "dataSource": "state:performanceData" }
}
]
}
]
}
} List/Detail Layout
List Page:
{
"route": "/items",
"sections": [
{
"type": "Container",
"children": [
{
"type": "PageHeader",
"title": "Items",
"actions": [
{
"type": "Button",
"label": "New Item",
"events": {
"on_click": [{ "type": "navigate", "to": "/items/new" }]
}
}
]
},
{
"type": "Table",
"dataSource": "state:items",
"columns": [
{ "key": "name", "label": "Name" },
{ "key": "status", "label": "Status" },
{ "key": "createdAt", "label": "Created" }
],
"events": {
"on_row_click": [
{ "type": "navigate", "to": "/items/{{$row.id}}" }
]
}
}
]
}
]
} Detail Page:
{
"route": "/items/:id",
"state": {
"item": { "type": "object", "default": null }
},
"hooks": {
"on_mount": [
{
"type": "call_api",
"api": "my-plugin.getItem",
"args": { "id": "{{params.id}}" },
"on_success": [
{ "type": "update_state", "path": "item", "from": "$response.data" }
]
}
]
},
"sections": [
{
"type": "Container",
"children": [
{
"type": "PageHeader",
"title": "{{state.item.name}}",
"backLink": "/items"
},
{
"type": "Conditional",
"condition": "{{state.item}}",
"then": {
"type": "Card",
"content": {
"type": "Flex",
"direction": "column",
"gap": "1rem",
"children": [
{ "type": "DataDisplay", "label": "Name", "value": "{{state.item.name}}" },
{ "type": "DataDisplay", "label": "Status", "value": "{{state.item.status}}" },
{ "type": "DataDisplay", "label": "Created", "value": "{{state.item.createdAt}}" }
]
}
},
"else": {
"type": "Skeleton"
}
}
]
}
} Form Page
{
"route": "/items/new",
"state": {
"formData": {
"type": "object",
"default": {
"name": "",
"description": "",
"category": ""
}
},
"isSubmitting": { "type": "boolean", "default": false }
},
"sections": [
{
"type": "Container",
"className": "p-6 max-w-2xl mx-auto",
"children": [
{
"type": "PageHeader",
"title": "Create Item",
"backLink": "/items"
},
{
"type": "Card",
"content": {
"type": "Form",
"id": "create-form",
"fields": [
{
"name": "name",
"fieldType": "text",
"label": "Name",
"placeholder": "Enter item name",
"bindTo": "formData.name",
"validation": {
"required": { "message": "Name is required" }
}
},
{
"name": "description",
"fieldType": "textarea",
"label": "Description",
"placeholder": "Enter description",
"bindTo": "formData.description"
},
{
"name": "category",
"fieldType": "select",
"label": "Category",
"bindTo": "formData.category",
"options": [
{ "value": "a", "label": "Category A" },
{ "value": "b", "label": "Category B" }
]
}
],
"events": {
"on_submit": [
{ "type": "set_loading", "target": "submit", "loading": true },
{
"type": "call_api",
"api": "my-plugin.createItem",
"args": { "data": "{{state.formData}}" },
"on_success": [
{ "type": "show_toast", "message": "Item created!", "level": "success" },
{ "type": "navigate", "to": "/items" }
],
"on_error": [
{ "type": "show_toast", "message": "Failed to create item", "level": "error" }
]
},
{ "type": "set_loading", "target": "submit", "loading": false }
]
}
}
}
]
}
]
} Multiple Pages
Plugins can define multiple pages:
{
"pages": [
{
"title": "Dashboard",
"route": "/dashboard",
"icon": "Home",
"show_in_menu": true,
"sections": [ ... ]
},
{
"title": "Items",
"route": "/items",
"icon": "List",
"show_in_menu": true,
"sections": [ ... ]
},
{
"title": "Settings",
"route": "/settings",
"icon": "Settings",
"show_in_menu": true,
"sections": [ ... ]
}
]
} Best Practices
Route Naming
// Good - namespaced routes
"/my-plugin/dashboard"
"/my-plugin/items"
"/my-plugin/items/:id"
// ❌ Avoid - may conflict
"/dashboard"
"/items" State Organization
// Good - organized state
"state": {
"data": { "type": "object" },
"ui": {
"type": "object",
"default": {
"isLoading": false,
"selectedTab": "all"
}
},
"form": {
"type": "object",
"default": { ... }
}
} Loading States
Always show loading feedback:
{
"type": "Conditional",
"condition": "{{state.isLoading}}",
"then": { "type": "Skeleton" },
"else": { "type": "Table", ... }
} Error Handling
Handle load failures:
"hooks": {
"on_mount": [
{
"type": "call_api",
"api": "getData",
"on_error": [
{ "type": "update_state", "path": "error", "from": "$error.message" }
]
}
]
} {
"type": "Conditional",
"condition": "{{state.error}}",
"then": {
"type": "Alert",
"variant": "destructive",
"title": "Error",
"message": "{{state.error}}"
}
} Next Steps
- Components - All available components
- Actions - All action types
- Building Plugins - Build and distribution