ObjectStackObjectStack

Portal

Portal protocol schemas

@module ui/portal

Portal Protocol — Metadata-driven external-user UI projection.

A Portal is not a new application or permission model. It is a

declarative projection of the existing app / view / action surface,

scoped to a route prefix, a set of profiles, and an optional anonymous

entry surface.

Five invariants this schema preserves:

  1. Zero business code — layout is an enum + plugin id; theme is tokens;

navigation is references to existing metadata.

  1. Data plane is untouched — portal cannot declare objects, fields,

flows, or permissions. Data API (/api/v1/data/...) is unaware of

portals.

  1. Portal ≠ permission boundary. The Profile is. Portals only narrow

the UI projection; hiding a view in navigation is UX, not security.

  1. Stackable — the same user/profile can be admitted by multiple

portals. Routing or a picker decides which one is rendered.

  1. Template-first — a template author ships customer.portal.ts and

the platform guarantees the rendering shell.

Architectural reach (consumer guidance, not part of the schema):

  • Dispatcher / HonoServer: at boot, enumerate portals and register

/<routePrefix>/* route families with a per-portal auth scope.

  • Auth middleware: admit the request if profile ∈ portal.profiles,

or it matches anonymousEntry.routes[*].

  • objectui LayoutDispatcher: select shell from layout.

  • objectui NavigationBuilder: render navigation (not the all-apps

grid).

  • objectui ThemeProvider: inject theme as CSS variables.

See framework issue

https://github.com/objectstack-ai/framework/issues/1294

for the design rationale.

Source: packages/spec/src/ui/portal.zod.ts

TypeScript Usage

import { Portal, PortalActionNavItem, PortalAnonymousEntry, PortalAnonymousRoute, PortalAuthMode, PortalDashboardNavItem, PortalLayout, PortalNavItem, PortalRateLimit, PortalSeo, PortalTheme, PortalUrlNavItem, PortalViewNavItem } from '@objectstack/spec/ui';
import type { Portal, PortalActionNavItem, PortalAnonymousEntry, PortalAnonymousRoute, PortalAuthMode, PortalDashboardNavItem, PortalLayout, PortalNavItem, PortalRateLimit, PortalSeo, PortalTheme, PortalUrlNavItem, PortalViewNavItem } from '@objectstack/spec/ui';

// Validate data
const result = Portal.parse(data);

Portal

Properties

PropertyTypeRequiredDescription
kindstringMetadata kind discriminator.
idstringPortal unique machine name (lowercase snake_case).
labelstringPortal display label.
descriptionstringoptionalDisplay label (plain string; i18n keys are auto-generated by the framework)
routePrefixstringRoot URL path for the portal (must start with "/").
domainstringoptionalOptional vanity domain (e.g. "support.acme.com").
layoutstring | string | string | stringShell layout for the portal.
themeObjectoptionalTheme tokens.
localestring | stringLocale resolution strategy.
seoObjectoptional
authModestring | string | string | stringAuthentication mode for the portal.
profilesstring[]Profiles admitted to the portal.
anonymousEntryObjectoptional
navigationObject | Object | Object | Object[]Flat list of portal entry points (references to existing metadata).
defaultRouteObjectoptionalLanding surface when the user hits the portal root.
embeddableboolean
allowedEmbedOriginsstring[]optional
activeboolean

PortalActionNavItem

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
actionRefstringReference to an existing action, e.g. "helpdesk_ticket.create".

PortalAnonymousEntry

Properties

PropertyTypeRequiredDescription
routesObject[]List of anonymous-accessible routes.
defaultRateLimitObjectoptional

PortalAnonymousRoute

Properties

PropertyTypeRequiredDescription
pathstringPath within the portal, e.g. "/submit" or "/kb".
viewRefstringoptionalReference to a public view (read-only).
actionRefstringoptionalReference to an action to perform anonymously (mutation).
rateLimitObjectoptionalRate-limit for anonymous traffic on this route.
captchabooleanRequire CAPTCHA / proof-of-work challenge before invoking.
bindIdentityFromFieldstringoptionalField name on the action input to use for magic-link identity binding (e.g. "customer_email").

PortalAuthMode

Union Options

This schema accepts one of the following structures:

Option 1

Type: string


Option 2

Type: string


Option 3

Type: string


Option 4

Type: string



PortalDashboardNavItem

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
dashboardNamestringExisting dashboard id.

PortalLayout

Union Options

This schema accepts one of the following structures:

Option 1

Type: string


Option 2

Type: string


Option 3

Type: string


Option 4

Type: string



PortalNavItem

Union Options

This schema accepts one of the following structures:

Option 1

Type: view

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
viewRefstringReference to an existing view, e.g. "helpdesk_ticket.list.my_tickets".

Option 2

Type: action

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
actionRefstringReference to an existing action, e.g. "helpdesk_ticket.create".

Option 3

Type: dashboard

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
dashboardNamestringExisting dashboard id.

Option 4

Type: url

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
urlstringAbsolute or root-relative URL.
targetEnum<'_self' | '_blank'>


PortalRateLimit

Properties

PropertyTypeRequiredDescription
rulestringRate-limit rule string, e.g. "5/hour/ip", "100/day/tenant".
scopeEnum<'ip' | 'tenant' | 'route'>Counter scope. "ip" buckets per requester; "tenant" per portal owner; "route" global per route.

PortalSeo

Properties

PropertyTypeRequiredDescription
titlestringoptionalDefault <title>.
descriptionstringoptionalDefault <meta name="description">.
openGraphImagestringoptionalDefault og:image URL.
robotsEnum<'index' | 'noindex'>

PortalTheme

Properties

PropertyTypeRequiredDescription
primaryColorstringoptionalPrimary brand color (hex, rgb, hsl). Mapped to --portal-primary.
accentColorstringoptionalAccent color used for highlights and CTAs.
backgroundColorstringoptionalPage background color.
surfaceColorstringoptionalCard / surface background color.
textColorstringoptionalPrimary text color.
logoUrlstringoptionalAbsolute or relative URL to the portal header logo (SVG or PNG).
faviconUrlstringoptionalAbsolute or relative URL to the portal favicon.
fontFamilystringoptionalCSS font-family stack for the portal.
customCssstringoptionalOPTIONAL escape hatch: raw CSS appended last. Discouraged; prefer tokens.

PortalUrlNavItem

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
urlstringAbsolute or root-relative URL.
targetEnum<'_self' | '_blank'>

PortalViewNavItem

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this portal nav item (lowercase snake_case).
labelstringDisplay label.
iconstringoptionalIcon name (Lucide).
ordernumberoptionalSort order; lower appears first.
badgestring | numberoptionalOptional badge (e.g. unread count).
typestring
viewRefstringReference to an existing view, e.g. "helpdesk_ticket.list.my_tickets".

On this page