| title | App Metadata |
|---|---|
| description | Define application containers with navigation, branding, and access control |
An App is a logical container that bundles objects, views, pages, and dashboards into a cohesive application experience. It defines the navigation structure, branding, and access permissions.
{/* os:check */}
const crmApp = {
name: 'crm',
label: 'CRM',
version: '1.0.0',
description: 'Customer Relationship Management',
icon: 'briefcase',
active: true,
branding: {
primaryColor: '#1a73e8',
logo: '/assets/crm-logo.svg',
favicon: '/assets/favicon.ico',
},
navigation: [
{ id: 'nav_accounts', type: 'object', label: 'Accounts', objectName: 'account', icon: 'building' },
{ id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'contact', icon: 'users' },
{ id: 'nav_opportunities', type: 'object', label: 'Opportunities', objectName: 'opportunity', icon: 'trending-up' },
{ id: 'nav_sales_dashboard', type: 'dashboard', label: 'Sales Dashboard', dashboardName: 'sales_overview', icon: 'bar-chart' },
],
requiredPermissions: ['crm_access'],
};| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
✅ | Machine name (snake_case) |
label |
string |
✅ | Display name |
version |
string |
optional | App version |
description |
string |
optional | App description |
icon |
string |
optional | App icon (Lucide) |
active |
boolean |
optional | Is app active (default: true) |
isDefault |
boolean |
optional | Is default app |
navigation |
NavigationItem[] |
optional | Navigation tree |
branding |
AppBranding |
optional | Visual customization |
requiredPermissions |
string[] |
optional | Required permissions to access |
homePageId |
string |
optional | ID of the navigation item to serve as the landing page |
objects |
unknown[] |
optional | Objects belonging to this app |
apis |
unknown[] |
optional | Custom APIs belonging to this app |
mobileNavigation |
object |
optional | Mobile-specific navigation |
The navigation tree supports nine item types, combined to create rich menu structures: object, dashboard, page, url, report, action, component, group and separator. Each is documented below.
type is the discriminator: a value outside that list is rejected with the full
set of valid ones, and every other key is checked against the branch you picked
rather than against all nine.
Links to an object's list view:
{ id: 'nav_accounts', type: 'object', label: 'Accounts', objectName: 'account', icon: 'building', viewName: 'all_accounts' }Three optional target fields refine where the entry lands. They are mutually exclusive — combining filters with recordId or viewName is rejected at validation, and the only tolerated pairing is the legacy recordId + viewName (where recordId wins and viewName is ignored):
viewName— anchor the entry to a named list view.recordId— deep-link straight to one record ("My Profile"); supports{current_user_id}/{current_org_id}template variables.filters— a one-off parameterized slice: the entry lands on the bare data surface (/:objectName/data) with each condition serialized as a removablefilter[<field>]=<value>URL chip, not anchored to any saved view. Use it for drill-throughs and "assigned to me"-style links instead of authoring a view; values support the same template variables. The surface shows what row-level permissions allow — it is not a security feature.
{ id: 'nav_my_open', type: 'object', label: 'My Open Deals', objectName: 'opportunity',
filters: { owner_id: '{current_user_id}', status: 'open' }, icon: 'user-check' }Links to a dashboard:
{ id: 'nav_analytics', type: 'dashboard', label: 'Analytics', dashboardName: 'sales_overview', icon: 'bar-chart' }Links to a custom page:
{ id: 'nav_settings', type: 'page', label: 'Settings', pageName: 'app_settings', icon: 'settings', params: { tab: 'general' } }Links to an external URL:
{ id: 'nav_help', type: 'url', label: 'Help Center', url: 'https://help.example.com', icon: 'help-circle', target: '_blank' }Groups items into collapsible sections with children:
{
id: 'grp_sales',
type: 'group',
label: 'Sales',
icon: 'dollar-sign',
expanded: true,
children: [
{ id: 'nav_accounts', type: 'object', label: 'Accounts', objectName: 'account', icon: 'building' },
{ id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'contact', icon: 'users' },
{ id: 'nav_opportunities', type: 'object', label: 'Opportunities', objectName: 'opportunity', icon: 'trending-up' },
],
}Links to a saved report:
{ id: 'nav_pipeline', type: 'report', label: 'Pipeline Report', reportName: 'sales_pipeline', icon: 'file-bar-chart' }Runs an action instead of navigating to a surface. The reference lives in a
nested actionDef block — actionName is not a top-level key on the item:
{
id: 'nav_import',
type: 'action',
label: 'Import Records',
icon: 'upload',
actionDef: {
actionName: 'bulk_import',
params: { objectName: 'account', mode: 'upsert' },
},
}actionDef accepts only actionName and params. params itself is open by
design — the action owns its own parameter contract, so the app schema does
not validate what goes inside it.
Renders a registered component. componentRef is a component-registry key, not
a file path:
{ id: 'nav_directory', type: 'component', label: 'Directory', componentRef: 'metadata:directory', icon: 'contact' }params is handed to the component as props and is open by design — the
props are the component's own contract.
A visual divider in the navigation list. It renders no target and carries no
label — the only keys it accepts are type, an optional id, and an optional
order:
{ type: 'separator', order: 30 }All navigation items except separator share these base properties. Every
item must declare a unique id (lowercase snake_case) — it is required by
the schema and is referenced by homePageId and mobileNavigation.bottomNavItems.
(On a separator, id is optional and the remaining properties below are
rejected — a divider has nothing to label, gate, or badge.)
| Property | Type | Description |
|---|---|---|
id |
string |
Unique identifier (snake_case, required) |
label |
string |
Display label |
icon |
string |
Icon name (Lucide) |
order |
number |
Sort order within the same level (lower = first) |
badge |
string | number |
Badge text or count displayed on the item |
visible |
Expression |
Visibility predicate (CEL expression) |
requiredPermissions |
string[] |
Permissions required to see/access this item |
requiresObject |
string |
Hide/disable unless the named object is registered |
requiresService |
string |
Hide/disable unless the named kernel service is registered |
Customize the visual appearance of the app:
branding: {
primaryColor: '#1a73e8', // Hex color code
logo: '/assets/logo.svg', // Logo URL
favicon: '/assets/icon.ico', // Favicon URL
}| Property | Type | Description |
|---|---|---|
primaryColor |
string |
Primary brand color (hex) |
logo |
string |
Logo image URL |
favicon |
string |
Favicon URL |
Configure mobile-specific navigation behavior:
mobileNavigation: {
mode: 'bottom_nav',
bottomNavItems: ['nav_home', 'nav_accounts', 'nav_contacts', 'nav_settings'],
}mode accepts 'drawer' (default), 'bottom_nav', or 'hamburger'. bottomNavItems lists the navigation item ids to surface in the bottom bar (max 5).
{/* os:check */}
const projectApp = {
name: 'project_management',
label: 'Project Management',
version: '2.0.0',
description: 'Track projects, tasks, and team workload',
icon: 'folder-kanban',
active: true,
branding: {
primaryColor: '#6366f1',
logo: '/assets/pm-logo.svg',
},
navigation: [
{
id: 'nav_home',
type: 'page',
label: 'Home',
pageName: 'pm_home',
icon: 'home',
},
{
id: 'grp_projects',
type: 'group',
label: 'Projects',
icon: 'folder',
expanded: true,
children: [
{ id: 'nav_projects', type: 'object', label: 'Projects', objectName: 'project', icon: 'folder' },
{ id: 'nav_tasks', type: 'object', label: 'Tasks', objectName: 'project_task', icon: 'check-square', viewName: 'task_board' },
{ id: 'nav_milestones', type: 'object', label: 'Milestones', objectName: 'milestone', icon: 'flag' },
],
},
{
id: 'grp_reports',
type: 'group',
label: 'Reports',
icon: 'bar-chart',
children: [
{ id: 'nav_overview', type: 'dashboard', label: 'Overview', dashboardName: 'project_overview', icon: 'layout-dashboard' },
{ id: 'nav_team_workload', type: 'dashboard', label: 'Team Workload', dashboardName: 'team_workload', icon: 'users' },
],
},
{
id: 'nav_docs',
type: 'url',
label: 'Documentation',
url: 'https://docs.example.com/pm',
icon: 'book-open',
target: '_blank',
},
],
requiredPermissions: ['pm_access'],
homePageId: 'nav_home',
};- Page Metadata — Custom pages referenced from navigation
- Dashboard Metadata — Dashboards referenced from navigation
- View Metadata — Views displayed within navigation items
- Permission Metadata — Access control for apps