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

json
{
  "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.

json
"title": "Dashboard"

Supports expressions:

json
"title": "{{state.userName}}'s Dashboard"

route

URL path for the page.

json
"route": "/dashboard"

Note: The plugin name is automatically prefixed, so /dashboard becomes /plugins/my-plugin/dashboard.

Route Patterns

PatternExampleDescription
Static/dashboardFixed path
Nested/settings/profileNested path
Parameter/items/:idDynamic segment
Wildcard/*Catch-all

Route Parameters

Access in expressions:

json
"route": "/items/:itemId",
"sections": [
  {
    "type": "Text",
    "content": "Viewing item: {{params.itemId}}"
  }
]

sections

Array of root component schemas defining the UI.

json
"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).

json
"icon": "LayoutDashboard"

Common icons:

  • Home, Settings, User, Users
  • FileText, FolderOpen, Database
  • ChartBar, Activity, Bell
  • Plus, Edit, Trash, Search

Browse all at lucide.dev.

state

Page state definition.

json
"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

TypeDescriptionDefault
stringText value""
numberNumeric value0
booleanTrue/falsefalse
objectKey-value map{}
arrayList of items[]

hooks

Page lifecycle hooks.

on_mount

Actions to execute when page loads.

json
"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.

json
"hooks": {
  "on_unmount": [
    {
      "type": "update_state",
      "path": "selectedItem",
      "from": "null"
    }
  ]
}

Common patterns:

  • Clear selections
  • Cancel pending requests
  • Save draft data

Layout Patterns

Simple Page

json
{
  "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

json
{
  "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:

json
{
  "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:

json
{
  "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

json
{
  "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:

json
{
  "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

json
// Good - namespaced routes
"/my-plugin/dashboard"
"/my-plugin/items"
"/my-plugin/items/:id"

// ❌ Avoid - may conflict
"/dashboard"
"/items"

State Organization

json
// 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:

json
{
  "type": "Conditional",
  "condition": "{{state.isLoading}}",
  "then": { "type": "Skeleton" },
  "else": { "type": "Table", ... }
}

Error Handling

Handle load failures:

json
"hooks": {
  "on_mount": [
    {
      "type": "call_api",
      "api": "getData",
      "on_error": [
        { "type": "update_state", "path": "error", "from": "$error.message" }
      ]
    }
  ]
}
json
{
  "type": "Conditional",
  "condition": "{{state.error}}",
  "then": {
    "type": "Alert",
    "variant": "destructive",
    "title": "Error",
    "message": "{{state.error}}"
  }
}

Next Steps