# Widgets and slots

Every place where an addon can inject UI: 82 admin pages and 27 client pages, each with its own page slots, 8 layout slots and 11 hand-placed named slots (579 slot names in total, wildcards not counted). Generated from `frontend/src/App.tsx`, `frontend/src/layouts/Shell.tsx` and the `<PluginSlot>` / `<PluginWidgets>` calls; a test fails when the frontend renders a position the docs do not list.

## Two ways to render

- **Native** (default): `body` is a JSON node tree (cards, tables, stats, forms, …) rendered with the panel's own components and theme. No addon JavaScript runs. Node reference: [UI schema](ui.md).
- **Iframe**: `frame: {src, height}` loads an HTML file from the addon's `assets/` in a sandboxed iframe (opaque origin, strict CSP). Needs `"iframe"` in `capabilities.ui`, approved by the admin at install. The frame talks to the panel through `postMessage`: `pelty:ready`, `pelty:resize`, `pelty:request` (own routes, plus GET on approved `bridge` prefixes), `pelty:navigate` and `pelty:toast`.

## Registering a slot contribution

List contributions in `addon.json` `ui.slots`. `order` sorts contributions in the same slot (lower first). Text that starts with `plugin.<addon-id>.` is translated from the manifest `i18n`. Page context is available as `{{ctx.*}}`.

```json
{
  "ui": {
    "slots": [
      {
        "body": {
          "text": "{{t:plugin.my-addon.badge}}",
          "type": "badge"
        },
        "order": 5,
        "slot": "admin.invoices.toolbar"
      },
      {
        "body": {
          "children": [
            {
              "label": "plugin.my-addon.score",
              "type": "stat",
              "value": "{{data.score.value}}"
            }
          ],
          "type": "card"
        },
        "data": {
          "score": {
            "get": "/api/v1/addons/my-addon/score?customer={{ctx.id}}"
          }
        },
        "slot": "admin.customers.detail-panel"
      },
      {
        "frame": {
          "height": 64,
          "src": "assets/banner.html"
        },
        "slot": "client.layout.banner"
      }
    ]
  }
}
```

The example addon's real manifest is on [Addon UI](ui-guide.md).

## Slot names

Page slots are `<area>.<route-key>.<position>`, with `area` = `admin` or `client`. Use `*` as route key for every page (`admin.*.toolbar`). Positions: `before`, `after`, `toolbar`, `row-actions`, `tabs`, `detail-panel`. Layout slots are `<area>.layout.<position>` with `header`, `sidebar`, `footer`, `banner`.

| Position | Where it renders | Context |
|---|---|---|
| `before` | Above the page content, below the layout banner | `ctx.route`, `ctx.id` on detail pages |
| `toolbar` | In the page header, next to the page's own buttons | same |
| `tabs` | Extra tabs after the page; `body.label` is the tab title | same |
| `detail-panel` | Cards below the content, only on pages with an id | same |
| `after` | Below the page content | same |
| `row-actions` | An extra actions column on the page's tables | `ctx.row_id`, `row` |
| `replace:<section>` | Replaces a section wrapped in `<Replaceable section>` | same |
| `layout.header` | Top bar, before the search (desktop) | `ctx.route` |
| `layout.sidebar` | Bottom of the sidebar | `ctx.route` |
| `layout.banner` | Above every page | `ctx.route` |
| `layout.footer` | Below every page | `ctx.route` |

Replaceable sections: none are wrapped yet, so `replace:` slots currently render nothing.

## Named slots

Fixed slots placed on specific pages. `render` slots take a node tree or frame (`ui.slots`). `widget` and `nav` slots take stat cards and menu links from webhook plugins (`ui.widgets`, `ui.menu`).

| Slot | Kind | Context | Rendered in |
|---|---|---|---|
| `admin.nav` | nav |  | `frontend/src/layouts/AdminLayout.tsx` |
| `client.nav` | nav |  | `frontend/src/layouts/ClientLayout.tsx` |
| `admin.customer.detail` | render | `ctx.customer_id` | `frontend/src/pages/admin/ClientExtras.tsx` |
| `admin.dashboard` | render |  | `frontend/src/pages/admin/Dashboard.tsx` |
| `admin.invoice.detail` | render | `ctx.invoice_id` | `frontend/src/pages/admin/Billing.tsx` |
| `admin.order.detail` | render | `ctx.order_id` | `frontend/src/pages/admin/OrderDetail.tsx` |
| `admin.service.detail` | render | `ctx.service_id` | `frontend/src/pages/admin/Servers.tsx` |
| `client.dashboard` | render |  | `frontend/src/pages/Dashboard.tsx` |
| `client.home` | render |  | `frontend/src/components/HomeWidgets.tsx` |
| `admin.dashboard` | widget |  | `frontend/src/pages/admin/Dashboard.tsx` |
| `client.dashboard` | widget |  | `frontend/src/pages/Dashboard.tsx` |

## Admin pages

| Page | Route key | Component | Slots |
|---|---|---|---|
| `/admin` | `dashboard` | AdminDashboardPage | `admin.dashboard.before` `admin.dashboard.after` `admin.dashboard.toolbar` `admin.dashboard.row-actions` `admin.dashboard.tabs` |
| `/admin/p/:plugin/:page` | `p-<plugin>-<page>` | AdminPluginPage | `admin.p-<plugin>-<page>.before` `admin.p-<plugin>-<page>.after` `admin.p-<plugin>-<page>.toolbar` `admin.p-<plugin>-<page>.row-actions` `admin.p-<plugin>-<page>.tabs` |
| `/admin/users` | `users` | AdminUsersPage | `admin.users.before` `admin.users.after` `admin.users.toolbar` `admin.users.row-actions` `admin.users.tabs` |
| `/admin/staff` | `staff` | AdminStaffPage | `admin.staff.before` `admin.staff.after` `admin.staff.toolbar` `admin.staff.row-actions` `admin.staff.tabs` |
| `/admin/campaigns` | `campaigns` | AdminCampaignsPage | `admin.campaigns.before` `admin.campaigns.after` `admin.campaigns.toolbar` `admin.campaigns.row-actions` `admin.campaigns.tabs` |
| `/admin/billing-tools` | `billing-tools` | AdminBillingToolsPage | `admin.billing-tools.before` `admin.billing-tools.after` `admin.billing-tools.toolbar` `admin.billing-tools.row-actions` `admin.billing-tools.tabs` |
| `/admin/gift-cards` | `gift-cards` | AdminGiftCardsPage | `admin.gift-cards.before` `admin.gift-cards.after` `admin.gift-cards.toolbar` `admin.gift-cards.row-actions` `admin.gift-cards.tabs` |
| `/admin/transactions` | `transactions` | AdminTransactionsPage | `admin.transactions.before` `admin.transactions.after` `admin.transactions.toolbar` `admin.transactions.row-actions` `admin.transactions.tabs` |
| `/admin/customers` | `customers` | AdminCustomersPage | `admin.customers.before` `admin.customers.after` `admin.customers.toolbar` `admin.customers.row-actions` `admin.customers.tabs` |
| `/admin/products` | `products` | AdminProductsPage | `admin.products.before` `admin.products.after` `admin.products.toolbar` `admin.products.row-actions` `admin.products.tabs` |
| `/admin/orders` | `orders` | AdminOrdersPage | `admin.orders.before` `admin.orders.after` `admin.orders.toolbar` `admin.orders.row-actions` `admin.orders.tabs` |
| `/admin/orders/:id` | `orders` | AdminOrderPage | `admin.orders.before` `admin.orders.after` `admin.orders.toolbar` `admin.orders.row-actions` `admin.orders.tabs` `admin.orders.detail-panel` |
| `/admin/invoices` | `invoices` | AdminInvoicesPage | `admin.invoices.before` `admin.invoices.after` `admin.invoices.toolbar` `admin.invoices.row-actions` `admin.invoices.tabs` |
| `/admin/invoices/:id` | `invoices` | AdminInvoicePage | `admin.invoices.before` `admin.invoices.after` `admin.invoices.toolbar` `admin.invoices.row-actions` `admin.invoices.tabs` `admin.invoices.detail-panel` |
| `/admin/services` | `services` | AdminServicesPage | `admin.services.before` `admin.services.after` `admin.services.toolbar` `admin.services.row-actions` `admin.services.tabs` |
| `/admin/domains` | `domains` | AdminDomainsPage | `admin.domains.before` `admin.domains.after` `admin.domains.toolbar` `admin.domains.row-actions` `admin.domains.tabs` |
| `/admin/domains/:id` | `domains` | AdminDomainPage | `admin.domains.before` `admin.domains.after` `admin.domains.toolbar` `admin.domains.row-actions` `admin.domains.tabs` `admin.domains.detail-panel` |
| `/admin/tickets` | `tickets` | AdminTicketsPage | `admin.tickets.before` `admin.tickets.after` `admin.tickets.toolbar` `admin.tickets.row-actions` `admin.tickets.tabs` |
| `/admin/tickets/:id` | `tickets` | AdminTicketPage | `admin.tickets.before` `admin.tickets.after` `admin.tickets.toolbar` `admin.tickets.row-actions` `admin.tickets.tabs` `admin.tickets.detail-panel` |
| `/admin/support-settings` | `support-settings` | AdminSupportSettingsPage | `admin.support-settings.before` `admin.support-settings.after` `admin.support-settings.toolbar` `admin.support-settings.row-actions` `admin.support-settings.tabs` |
| `/admin/coupons` | `coupons` | AdminCouponsPage | `admin.coupons.before` `admin.coupons.after` `admin.coupons.toolbar` `admin.coupons.row-actions` `admin.coupons.tabs` |
| `/admin/order-forms` | `order-forms` | AdminOrderFormsPage | `admin.order-forms.before` `admin.order-forms.after` `admin.order-forms.toolbar` `admin.order-forms.row-actions` `admin.order-forms.tabs` |
| `/admin/quotes` | `quotes` | AdminQuotesPage | `admin.quotes.before` `admin.quotes.after` `admin.quotes.toolbar` `admin.quotes.row-actions` `admin.quotes.tabs` |
| `/admin/affiliates` | `affiliates` | AdminAffiliatesPage | `admin.affiliates.before` `admin.affiliates.after` `admin.affiliates.toolbar` `admin.affiliates.row-actions` `admin.affiliates.tabs` |
| `/admin/downloads` | `downloads` | AdminDownloadsPage | `admin.downloads.before` `admin.downloads.after` `admin.downloads.toolbar` `admin.downloads.row-actions` `admin.downloads.tabs` |
| `/admin/email-log` | `email-log` | AdminEmailLogPage | `admin.email-log.before` `admin.email-log.after` `admin.email-log.toolbar` `admin.email-log.row-actions` `admin.email-log.tabs` |
| `/admin/servers` | `servers` | AdminServersPage | `admin.servers.before` `admin.servers.after` `admin.servers.toolbar` `admin.servers.row-actions` `admin.servers.tabs` |
| `/admin/module-queue` | `module-queue` | AdminModuleQueuePage | `admin.module-queue.before` `admin.module-queue.after` `admin.module-queue.toolbar` `admin.module-queue.row-actions` `admin.module-queue.tabs` |
| `/admin/module-log` | `module-log` | AdminModuleLogPage | `admin.module-log.before` `admin.module-log.after` `admin.module-log.toolbar` `admin.module-log.row-actions` `admin.module-log.tabs` |
| `/admin/cancellations` | `cancellations` | AdminCancellationsPage | `admin.cancellations.before` `admin.cancellations.after` `admin.cancellations.toolbar` `admin.cancellations.row-actions` `admin.cancellations.tabs` |
| `/admin/jobs` | `jobs` | AdminJobsPage | `admin.jobs.before` `admin.jobs.after` `admin.jobs.toolbar` `admin.jobs.row-actions` `admin.jobs.tabs` |
| `/admin/audit` | `audit` | AdminAuditPage | `admin.audit.before` `admin.audit.after` `admin.audit.toolbar` `admin.audit.row-actions` `admin.audit.tabs` |
| `/admin/themes` | `themes` | AdminThemesPage | `admin.themes.before` `admin.themes.after` `admin.themes.toolbar` `admin.themes.row-actions` `admin.themes.tabs` |
| `/admin/addons` | `addons` | AdminAddonsPage | `admin.addons.before` `admin.addons.after` `admin.addons.toolbar` `admin.addons.row-actions` `admin.addons.tabs` |
| `/admin/website` | `website` | AdminWebsitePage | `admin.website.before` `admin.website.after` `admin.website.toolbar` `admin.website.row-actions` `admin.website.tabs` |
| `/admin/plugins` | `plugins` | AdminPluginsPage | `admin.plugins.before` `admin.plugins.after` `admin.plugins.toolbar` `admin.plugins.row-actions` `admin.plugins.tabs` |
| `/admin/plugins/:id` | `plugins` | AdminPluginDetailPage | `admin.plugins.before` `admin.plugins.after` `admin.plugins.toolbar` `admin.plugins.row-actions` `admin.plugins.tabs` `admin.plugins.detail-panel` |
| `/admin/plugins/:id/deliveries` | `plugins-deliveries` | AdminPluginDeliveriesPage | `admin.plugins-deliveries.before` `admin.plugins-deliveries.after` `admin.plugins-deliveries.toolbar` `admin.plugins-deliveries.row-actions` `admin.plugins-deliveries.tabs` `admin.plugins-deliveries.detail-panel` |
| `/admin/gateways` | `gateways` | AdminGatewaysPage | `admin.gateways.before` `admin.gateways.after` `admin.gateways.toolbar` `admin.gateways.row-actions` `admin.gateways.tabs` |
| `/admin/tax` | `tax` | AdminTaxPage | `admin.tax.before` `admin.tax.after` `admin.tax.toolbar` `admin.tax.row-actions` `admin.tax.tabs` |
| `/admin/overdue` | `overdue` | AdminOverduePage | `admin.overdue.before` `admin.overdue.after` `admin.overdue.toolbar` `admin.overdue.row-actions` `admin.overdue.tabs` |
| `/admin/email-templates` | `email-templates` | AdminEmailTemplatesPage | `admin.email-templates.before` `admin.email-templates.after` `admin.email-templates.toolbar` `admin.email-templates.row-actions` `admin.email-templates.tabs` |
| `/admin/content` | `content` | AdminContentPage | `admin.content.before` `admin.content.after` `admin.content.toolbar` `admin.content.row-actions` `admin.content.tabs` |
| `/admin/client-groups` | `client-groups` | AdminClientGroupsPage | `admin.client-groups.before` `admin.client-groups.after` `admin.client-groups.toolbar` `admin.client-groups.row-actions` `admin.client-groups.tabs` |
| `/admin/fraud` | `fraud` | AdminFraudPage | `admin.fraud.before` `admin.fraud.after` `admin.fraud.toolbar` `admin.fraud.row-actions` `admin.fraud.tabs` |
| `/admin/security` | `security` | AdminSecurityPage | `admin.security.before` `admin.security.after` `admin.security.toolbar` `admin.security.row-actions` `admin.security.tabs` |
| `/admin/oauth-clients` | `oauth-clients` | AdminOAuthClientsPage | `admin.oauth-clients.before` `admin.oauth-clients.after` `admin.oauth-clients.toolbar` `admin.oauth-clients.row-actions` `admin.oauth-clients.tabs` |
| `/admin/saml` | `saml` | AdminSamlPage | `admin.saml.before` `admin.saml.after` `admin.saml.toolbar` `admin.saml.row-actions` `admin.saml.tabs` |
| `/admin/gdpr` | `gdpr` | AdminGdprPage | `admin.gdpr.before` `admin.gdpr.after` `admin.gdpr.toolbar` `admin.gdpr.row-actions` `admin.gdpr.tabs` |
| `/admin/reports` | `reports` | AdminReportsPage | `admin.reports.before` `admin.reports.after` `admin.reports.toolbar` `admin.reports.row-actions` `admin.reports.tabs` |
| `/admin/mail-settings` | `mail-settings` | AdminMailSettingsPage | `admin.mail-settings.before` `admin.mail-settings.after` `admin.mail-settings.toolbar` `admin.mail-settings.row-actions` `admin.mail-settings.tabs` |
| `/admin/sessions` | `sessions` | SessionsPage | `admin.sessions.before` `admin.sessions.after` `admin.sessions.toolbar` `admin.sessions.row-actions` `admin.sessions.tabs` |
| `/admin/api-keys` | `api-keys` | AdminApiKeysPage | `admin.api-keys.before` `admin.api-keys.after` `admin.api-keys.toolbar` `admin.api-keys.row-actions` `admin.api-keys.tabs` |
| `/admin/developer-docs` | `developer-docs` | DeveloperDocsPage | `admin.developer-docs.before` `admin.developer-docs.after` `admin.developer-docs.toolbar` `admin.developer-docs.row-actions` `admin.developer-docs.tabs` |
| `/admin/network-status` | `network-status` | AdminNetworkStatusPage | `admin.network-status.before` `admin.network-status.after` `admin.network-status.toolbar` `admin.network-status.row-actions` `admin.network-status.tabs` |
| `/admin/system-health` | `system-health` | AdminSystemHealthPage | `admin.system-health.before` `admin.system-health.after` `admin.system-health.toolbar` `admin.system-health.row-actions` `admin.system-health.tabs` |
| `/admin/translations` | `translations` | AdminTranslationsPage | `admin.translations.before` `admin.translations.after` `admin.translations.toolbar` `admin.translations.row-actions` `admin.translations.tabs` |
| `/admin/customers/:id` | `customers` | AdminClientSummaryPage | `admin.customers.before` `admin.customers.after` `admin.customers.toolbar` `admin.customers.row-actions` `admin.customers.tabs` `admin.customers.detail-panel` |
| `/admin/my-notes` | `my-notes` | AdminMyNotesPage | `admin.my-notes.before` `admin.my-notes.after` `admin.my-notes.toolbar` `admin.my-notes.row-actions` `admin.my-notes.tabs` |
| `/admin/noticeboard` | `noticeboard` | AdminNoticeboardPage | `admin.noticeboard.before` `admin.noticeboard.after` `admin.noticeboard.toolbar` `admin.noticeboard.row-actions` `admin.noticeboard.tabs` |
| `/admin/tracked-links` | `tracked-links` | AdminTrackedLinksPage | `admin.tracked-links.before` `admin.tracked-links.after` `admin.tracked-links.toolbar` `admin.tracked-links.row-actions` `admin.tracked-links.tabs` |
| `/admin/calendar` | `calendar` | AdminCalendarPage | `admin.calendar.before` `admin.calendar.after` `admin.calendar.toolbar` `admin.calendar.row-actions` `admin.calendar.tabs` |
| `/admin/general-settings` | `general-settings` | AdminGeneralSettingsPage | `admin.general-settings.before` `admin.general-settings.after` `admin.general-settings.toolbar` `admin.general-settings.row-actions` `admin.general-settings.tabs` |
| `/admin/login-history` | `login-history` | AdminLoginHistoryPage | `admin.login-history.before` `admin.login-history.after` `admin.login-history.toolbar` `admin.login-history.row-actions` `admin.login-history.tabs` |
| `/admin/backups` | `backups` | AdminBackupsPage | `admin.backups.before` `admin.backups.after` `admin.backups.toolbar` `admin.backups.row-actions` `admin.backups.tabs` |
| `/admin/notifications` | `notifications` | AdminNotificationsPage | `admin.notifications.before` `admin.notifications.after` `admin.notifications.toolbar` `admin.notifications.row-actions` `admin.notifications.tabs` |
| `/admin/client-fields` | `client-fields` | AdminClientFieldsPage | `admin.client-fields.before` `admin.client-fields.after` `admin.client-fields.toolbar` `admin.client-fields.row-actions` `admin.client-fields.tabs` |
| `/admin/todos` | `todos` | AdminTodosPage | `admin.todos.before` `admin.todos.after` `admin.todos.toolbar` `admin.todos.row-actions` `admin.todos.tabs` |
| `/admin/projects` | `projects` | AdminProjectsPage | `admin.projects.before` `admin.projects.after` `admin.projects.toolbar` `admin.projects.row-actions` `admin.projects.tabs` |
| `/admin/marketplace` | `marketplace` | AdminMarketplacePage | `admin.marketplace.before` `admin.marketplace.after` `admin.marketplace.toolbar` `admin.marketplace.row-actions` `admin.marketplace.tabs` |
| `/admin/kyc` | `kyc` | AdminKycPage | `admin.kyc.before` `admin.kyc.after` `admin.kyc.toolbar` `admin.kyc.row-actions` `admin.kyc.tabs` |
| `/admin/getting-started` | `getting-started` | AdminGettingStartedPage | `admin.getting-started.before` `admin.getting-started.after` `admin.getting-started.toolbar` `admin.getting-started.row-actions` `admin.getting-started.tabs` |
| `/admin/projects/:id` | `projects` | AdminProjectDetailPage | `admin.projects.before` `admin.projects.after` `admin.projects.toolbar` `admin.projects.row-actions` `admin.projects.tabs` `admin.projects.detail-panel` |
| `/admin/currencies` | `currencies` | AdminCurrenciesPage | `admin.currencies.before` `admin.currencies.after` `admin.currencies.toolbar` `admin.currencies.row-actions` `admin.currencies.tabs` |
| `/admin/legal-entities` | `legal-entities` | AdminLegalEntitiesPage | `admin.legal-entities.before` `admin.legal-entities.after` `admin.legal-entities.toolbar` `admin.legal-entities.row-actions` `admin.legal-entities.tabs` |
| `/admin/billable-items` | `billable-items` | AdminBillableItemsPage | `admin.billable-items.before` `admin.billable-items.after` `admin.billable-items.toolbar` `admin.billable-items.row-actions` `admin.billable-items.tabs` |
| `/admin/csv-import` | `csv-import` | AdminCsvImportPage | `admin.csv-import.before` `admin.csv-import.after` `admin.csv-import.toolbar` `admin.csv-import.row-actions` `admin.csv-import.tabs` |
| `/admin/live-chat` | `live-chat` | AdminLiveChatPage | `admin.live-chat.before` `admin.live-chat.after` `admin.live-chat.toolbar` `admin.live-chat.row-actions` `admin.live-chat.tabs` |
| `/admin/bundles` | `bundles` | AdminBundlesPage | `admin.bundles.before` `admin.bundles.after` `admin.bundles.toolbar` `admin.bundles.row-actions` `admin.bundles.tabs` |
| `/admin/usage-metrics` | `usage-metrics` | AdminUsageMetricsPage | `admin.usage-metrics.before` `admin.usage-metrics.after` `admin.usage-metrics.toolbar` `admin.usage-metrics.row-actions` `admin.usage-metrics.tabs` |
| `/admin/settings` | `settings` | HubPage | `admin.settings.before` `admin.settings.after` `admin.settings.toolbar` `admin.settings.row-actions` `admin.settings.tabs` |
| `/admin/hub/:id` | `hub-<id>` | HubPage | `admin.hub-<id>.before` `admin.hub-<id>.after` `admin.hub-<id>.toolbar` `admin.hub-<id>.row-actions` `admin.hub-<id>.tabs` |

## Client area pages

| Page | Route key | Component | Slots |
|---|---|---|---|
| `/dashboard` | `dashboard` | DashboardPage | `client.dashboard.before` `client.dashboard.after` `client.dashboard.toolbar` `client.dashboard.row-actions` `client.dashboard.tabs` |
| `/hub/:id` | `hub-<id>` | HubPage | `client.hub-<id>.before` `client.hub-<id>.after` `client.hub-<id>.toolbar` `client.hub-<id>.row-actions` `client.hub-<id>.tabs` |
| `/sessions` | `sessions` | SessionsPage | `client.sessions.before` `client.sessions.after` `client.sessions.toolbar` `client.sessions.row-actions` `client.sessions.tabs` |
| `/api-keys` | `api-keys` | ApiKeysPage | `client.api-keys.before` `client.api-keys.after` `client.api-keys.toolbar` `client.api-keys.row-actions` `client.api-keys.tabs` |
| `/connected-apps` | `connected-apps` | ConnectedAppsPage | `client.connected-apps.before` `client.connected-apps.after` `client.connected-apps.toolbar` `client.connected-apps.row-actions` `client.connected-apps.tabs` |
| `/services` | `services` | ClientServicesPage | `client.services.before` `client.services.after` `client.services.toolbar` `client.services.row-actions` `client.services.tabs` |
| `/orders/:id` | `orders` | OrderStatusPage | `client.orders.before` `client.orders.after` `client.orders.toolbar` `client.orders.row-actions` `client.orders.tabs` `client.orders.detail-panel` |
| `/account` | `account` | AccountPage | `client.account.before` `client.account.after` `client.account.toolbar` `client.account.row-actions` `client.account.tabs` |
| `/invoices` | `invoices` | ClientInvoicesPage | `client.invoices.before` `client.invoices.after` `client.invoices.toolbar` `client.invoices.row-actions` `client.invoices.tabs` |
| `/billing` | `billing` | ClientBillingPage | `client.billing.before` `client.billing.after` `client.billing.toolbar` `client.billing.row-actions` `client.billing.tabs` |
| `/invoices/:id` | `invoices` | ClientInvoicePage | `client.invoices.before` `client.invoices.after` `client.invoices.toolbar` `client.invoices.row-actions` `client.invoices.tabs` `client.invoices.detail-panel` |
| `/invoices/:id/pay` | `invoices-pay` | ClientPayInvoicePage | `client.invoices-pay.before` `client.invoices-pay.after` `client.invoices-pay.toolbar` `client.invoices-pay.row-actions` `client.invoices-pay.tabs` `client.invoices-pay.detail-panel` |
| `/domains` | `domains` | ClientDomainsPage | `client.domains.before` `client.domains.after` `client.domains.toolbar` `client.domains.row-actions` `client.domains.tabs` |
| `/domains/:id` | `domains` | ClientDomainPage | `client.domains.before` `client.domains.after` `client.domains.toolbar` `client.domains.row-actions` `client.domains.tabs` `client.domains.detail-panel` |
| `/tickets` | `tickets` | ClientTicketsPage | `client.tickets.before` `client.tickets.after` `client.tickets.toolbar` `client.tickets.row-actions` `client.tickets.tabs` |
| `/tickets/:id` | `tickets` | ClientTicketPage | `client.tickets.before` `client.tickets.after` `client.tickets.toolbar` `client.tickets.row-actions` `client.tickets.tabs` `client.tickets.detail-panel` |
| `/projects` | `projects` | ClientProjectsPage | `client.projects.before` `client.projects.after` `client.projects.toolbar` `client.projects.row-actions` `client.projects.tabs` |
| `/verification` | `verification` | ClientVerificationPage | `client.verification.before` `client.verification.after` `client.verification.toolbar` `client.verification.row-actions` `client.verification.tabs` |
| `/projects/:id` | `projects` | ClientProjectDetailPage | `client.projects.before` `client.projects.after` `client.projects.toolbar` `client.projects.row-actions` `client.projects.tabs` `client.projects.detail-panel` |
| `/quotes` | `quotes` | ClientQuotesPage | `client.quotes.before` `client.quotes.after` `client.quotes.toolbar` `client.quotes.row-actions` `client.quotes.tabs` |
| `/quotes/:id` | `quotes` | ClientQuotePage | `client.quotes.before` `client.quotes.after` `client.quotes.toolbar` `client.quotes.row-actions` `client.quotes.tabs` `client.quotes.detail-panel` |
| `/downloads` | `downloads` | ClientDownloadsPage | `client.downloads.before` `client.downloads.after` `client.downloads.toolbar` `client.downloads.row-actions` `client.downloads.tabs` |
| `/emails` | `emails` | ClientEmailHistoryPage | `client.emails.before` `client.emails.after` `client.emails.toolbar` `client.emails.row-actions` `client.emails.tabs` |
| `/affiliate` | `affiliate` | ClientAffiliatePage | `client.affiliate.before` `client.affiliate.after` `client.affiliate.toolbar` `client.affiliate.row-actions` `client.affiliate.tabs` |
| `/network-status` | `network-status` | NetworkStatusPage | `client.network-status.before` `client.network-status.after` `client.network-status.toolbar` `client.network-status.row-actions` `client.network-status.tabs` |
| `/my-details` | `my-details` | ClientFieldsPage | `client.my-details.before` `client.my-details.after` `client.my-details.toolbar` `client.my-details.row-actions` `client.my-details.tabs` |
| `/p/:plugin/:page` | `p-<plugin>-<page>` | ClientPluginPage | `client.p-<plugin>-<page>.before` `client.p-<plugin>-<page>.after` `client.p-<plugin>-<page>.toolbar` `client.p-<plugin>-<page>.row-actions` `client.p-<plugin>-<page>.tabs` |

A key with `<param>` contains the actual value of that URL segment (`/admin/hub/system` → `hub-system`). Ids (numbers, UUIDs, long hex, `ABC-123`) are always dropped.

## Example: native widget and iframe widget

```json
{
  "body": {
    "text": "{{t:plugin.integration-guide.hello}}",
    "type": "alert"
  },
  "slot": "client.dashboard"
}
```

```html
<!-- assets/banner.html -->
<!doctype html><meta charset="utf-8">
<p id="msg">Loading…</p>
<script>
  const send = (m) => parent.postMessage(m, "*");
  addEventListener("message", (e) => {
    if (e.data?.type === "pelty:response" && e.data.id === 1) document.getElementById("msg").textContent = e.data.body.message;
  });
  send({ type: "pelty:ready" });
  send({ type: "pelty:request", id: 1, method: "GET", path: "/api/v1/addons/my-addon/hello" });
  send({ type: "pelty:resize", height: document.body.scrollHeight });
</script>
```

