Skip to main content

App Schema Reference

This document is the definitive reference for Stack9 App structure and properties. It is based on the canonical schema validator located at packages/stack9-sdk/src/schema/validators/v2/AppSchemaValidator.ts.

App Structure Overview

Apps in Stack9 follow a hierarchical structure with strict nesting rules and node types. The maximum depth is 4 levels with specific constraints at each level.

Nesting Hierarchy

Level 1: APP_NODE (root)
└─ Level 2: LINK or MENU_GROUP
└─ Level 3: LINK or MENU_GROUP (only if parent is MENU_GROUP)
└─ Level 4: LINK only (no further nesting allowed)

Base App Properties (Root Level)

The root app object (Level 1) must have nodeType: "appNode" and supports the following properties:

PropertyTypeRequiredDescriptionValidation
namestringYesDisplay name of the app in navigationAny string
keystringYesUnique identifier for the appAny string
nodeTypestringYesMust be "appNode" for root levelExact value: "appNode"
modulestringNoModule identifier if app belongs to a moduleAny string
ordernumberNoDisplay order in sidebar (lower numbers appear first)Any number
descriptionstringNoApp description for documentation/tooltipsAny string
iconstringNoIcon name (e.g., Ant Design icon names)Any string
visibilitystringNoAccess visibility levelSee AppVisibility enum
themestringNoTheme identifier for stylingAny string
childrenarrayNoNavigation items (Level 2 nodes)Array of child nodes

Node Types (SitemapNodeType Enum)

The system supports exactly three node types with specific use cases:

ValueCanonical StringLevelDescription
APP_NODE"appNode"1 onlyRoot level app container
MENU_GROUP"menuGroup"2, 3Collapsible group of navigation items
LINK"link"2, 3, 4Direct navigation link to a screen

Important: The canonical values are "appNode", "menuGroup", and "link" (all lowercase).

AppVisibility Enum

Controls who can see and access the app:

ValueDescriptionUse Case
"PUBLIC"Available to all users including unauthenticatedPublic-facing features
"EXTERNAL"Available to authenticated external usersPartner/customer portals
"INTERNAL"Only for internal authenticated usersAdmin and internal tools

Child Node Properties

Level 2 Nodes (Direct Children of APP_NODE)

Level 2 nodes can be either LINK or MENU_GROUP:

PropertyTypeRequiredDescriptionValidation
keystringYesUnique identifier within siblingsLowercase token only
namestringYesDisplay text in navigationAny string
nodeTypestringYesMust be "link"Exact value: "link"
linkstringYesURL path for navigationAny string
descriptionstringNoTooltip or additional infoAny string

Link Convention for SimpleCrud Screens: When linking to a simpleCrud screen, the link property should follow this pattern:

/app_key/screen_route/list

Where:

  • app_key is the app's key property
  • screen_route is the screen's route field from the screen configuration
  • /list is appended to show the list view

Example: If your screen has "route": "customer-order" and belongs to app "sales", the link should be "/sales/customer-order/list"

PropertyTypeRequiredDescriptionValidation
keystringYesUnique identifier within siblingsLowercase token only
namestringYesDisplay text for group headerAny string
nodeTypestringYesMust be "menuGroup"Exact value: "menuGroup"
childrenarrayConditionalRequired when nodeType is MENU_GROUPArray of Level 3 nodes
descriptionstringNoNot used for MENU_GROUPAny string (ignored)

Level 3 Nodes (Children of MENU_GROUP)

Level 3 nodes can also be either LINK or MENU_GROUP:

Same structure as Level 2 LINK nodes:

PropertyTypeRequiredDescription
keystringYesUnique identifier within siblings
namestringYesDisplay text in navigation
nodeTypestringYesMust be "link"
linkstringYesURL path for navigation
descriptionstringNoTooltip or additional info
PropertyTypeRequiredDescriptionValidation
keystringYesUnique identifier within siblingsLowercase token only
namestringYesDisplay text for group headerAny string
nodeTypestringYesMust be "menuGroup"Exact value: "menuGroup"
childrenarrayConditionalRequired when nodeType is MENU_GROUPArray of Level 4 LINK nodes only

Level 4 Nodes (Children of Nested MENU_GROUP)

Level 4 nodes can ONLY be LINK nodes. No further nesting is allowed.

PropertyTypeRequiredDescription
keystringYesUnique identifier within siblings
namestringYesDisplay text in navigation
nodeTypestringYesMust be "link"
linkstringYesURL path for navigation
descriptionstringNoTooltip or additional info

Validation Rules

Key Validation

All key properties must follow these rules:

  • Format: Lowercase token (letters, numbers, underscores)
  • Case: Automatically converted to lowercase
  • Uniqueness: Must be unique among siblings at the same level
  • Required: Always required for all node types

Conditional Properties

  • link property: Required when nodeType is "link", not allowed for other types
  • children property: Required when nodeType is "menuGroup", not allowed for "link"
  • description property: Only meaningful for LINK nodes

Unique Keys Within Siblings

The validator enforces unique keys at each level:

  • All Level 2 children must have unique keys
  • All Level 3 children within each MENU_GROUP must have unique keys
  • All Level 4 children within each nested MENU_GROUP must have unique keys

Complete Example with All Levels

{
"name": "Customer Management",
"key": "crm",
"nodeType": "appNode",
"description": "Complete CRM functionality",
"icon": "UserOutlined",
"visibility": "PUBLIC",
"theme": "default",
"order": 10,
"children": [
{
"key": "dashboard",
"name": "Dashboard",
"nodeType": "link",
"link": "/crm/dashboard",
"description": "CRM overview dashboard"
},
{
"key": "customers",
"name": "Customers",
"nodeType": "menuGroup",
"children": [
{
"key": "customer_list",
"name": "Customer List",
"nodeType": "link",
"link": "/crm/customer-list/list",
"description": "View all customers"
},
{
"key": "customer_segments",
"name": "Segments",
"nodeType": "link",
"link": "/crm/segments"
},
{
"key": "advanced",
"name": "Advanced",
"nodeType": "menuGroup",
"children": [
{
"key": "bulk_operations",
"name": "Bulk Operations",
"nodeType": "link",
"link": "/crm/bulk-ops"
},
{
"key": "merge_customers",
"name": "Merge Customers",
"nodeType": "link",
"link": "/crm/merge"
}
]
}
]
}
]
}

This example demonstrates:

  • Level 1: APP_NODE root with all optional properties
  • Level 2: Both LINK (dashboard) and MENU_GROUP (customers)
  • Level 3: Mix of LINK nodes and nested MENU_GROUP (advanced)
  • Level 4: LINK nodes only (bulk_operations, merge_customers)

Visual Hierarchy

📊 Customer Management (APP_NODE - Level 1)
├── 🔗 Dashboard (LINK - Level 2)
└── 📁 Customers (MENU_GROUP - Level 2)
├── 🔗 Customer List (LINK - Level 3)
├── 🔗 Segments (LINK - Level 3)
└── 📁 Advanced (MENU_GROUP - Level 3)
├── 🔗 Bulk Operations (LINK - Level 4)
└── 🔗 Merge Customers (LINK - Level 4)

Linking to Screens

When creating links to simpleCrud screens, always use the screen's route field (not the key field) in the URL path:

Pattern:

/app_key/screen_route/list

Important Notes:

  • Screen route fields typically use hyphens (e.g., customer-order, service-offer)
  • Screen key fields typically use underscores (e.g., customer_order, service_offer)
  • App links must use the route value, not the key value
  • Always append /list to show the list view of the screen

Example:

Given a screen configuration:

{
"head": {
"key": "customer_order",
"route": "customer-order",
"app": "sales"
}
}

The app link should be:

{
"key": "orders_link",
"name": "Customer Orders",
"nodeType": "link",
"link": "/sales/customer-order/list" // ✅ Uses route, not key
}

Common Mistakes:

// ❌ Wrong - using key instead of route
"link": "/sales/customer_order/list"

// ❌ Wrong - missing /list suffix
"link": "/sales/customer-order"

// ✅ Correct
"link": "/sales/customer-order/list"

Common Validation Errors

Invalid Node Type

// ❌ Wrong - using incorrect casing
{
"nodeType": "AppNode" // Must be "appNode"
}

// ❌ Wrong - using incorrect value
{
"nodeType": "GROUP" // Must be "menuGroup"
}

Missing Required Properties

// ❌ Wrong - LINK without link property
{
"key": "customers",
"name": "Customers",
"nodeType": "link"
// Missing required "link" property
}

// ❌ Wrong - MENU_GROUP without children
{
"key": "customers",
"name": "Customers",
"nodeType": "menuGroup"
// Missing required "children" property
}

Invalid Nesting

// ❌ Wrong - Level 4 with MENU_GROUP
{
"nodeType": "menuGroup",
"children": [
{
"nodeType": "menuGroup",
"children": [
{
"nodeType": "menuGroup", // ❌ Level 4 cannot be MENU_GROUP
"children": [...]
}
]
}
]
}

Non-Unique Keys

// ❌ Wrong - duplicate keys at same level
{
"children": [
{
"key": "customers",
"name": "All Customers"
},
{
"key": "customers", // ❌ Duplicate key
"name": "VIP Customers"
}
]
}

API Reference

Validator Class

The canonical validator is implemented in:

// packages/stack9-sdk/src/schema/validators/v2/AppSchemaValidator.ts
export class AppSchemaValidator {
public validator: Joi.ObjectSchema
public validate(data: any): ValidationResult
}

TypeScript Types

// From packages/stack9-sdk/src/models/core/S9App.ts

export enum SitemapNodeType {
APP_NODE = 'appNode',
MENU_GROUP = 'menuGroup',
LINK = 'link'
}

export enum AppVisibility {
PUBLIC = 'PUBLIC',
EXTERNAL = 'EXTERNAL',
INTERNAL = 'INTERNAL'
}

export interface S9App {
name: string;
key: string;
nodeType: SitemapNodeType;
module?: string;
order?: number;
description?: string;
icon?: string;
visibility?: AppVisibility;
theme?: string;
children?: S9Sitemap[];
}

Best Practices

  1. Use descriptive keys: Choose meaningful, semantic keys like customer_management instead of cm
  2. Consistent naming: Use the same naming convention across all apps (e.g., snake_case for keys)
  3. Follow screen routing convention: For simpleCrud screens, always use /app_key/screen_route/list format where screen_route matches the screen's route field (use hyphens, not underscores)
  4. Logical grouping: Group related features under MENU_GROUP nodes
  5. Limit nesting: Avoid deep nesting even though 4 levels are supported
  6. Set visibility appropriately: Use INTERNAL for admin features, PUBLIC for customer-facing
  7. Order strategically: Use the order property to control sidebar arrangement
  8. Validate before deployment: Use the AppSchemaValidator to validate your app definitions

See Also