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:
| Property | Type | Required | Description | Validation |
|---|---|---|---|---|
name | string | Yes | Display name of the app in navigation | Any string |
key | string | Yes | Unique identifier for the app | Any string |
nodeType | string | Yes | Must be "appNode" for root level | Exact value: "appNode" |
module | string | No | Module identifier if app belongs to a module | Any string |
order | number | No | Display order in sidebar (lower numbers appear first) | Any number |
description | string | No | App description for documentation/tooltips | Any string |
icon | string | No | Icon name (e.g., Ant Design icon names) | Any string |
visibility | string | No | Access visibility level | See AppVisibility enum |
theme | string | No | Theme identifier for styling | Any string |
children | array | No | Navigation items (Level 2 nodes) | Array of child nodes |
Node Types (SitemapNodeType Enum)
The system supports exactly three node types with specific use cases:
| Value | Canonical String | Level | Description |
|---|---|---|---|
APP_NODE | "appNode" | 1 only | Root level app container |
MENU_GROUP | "menuGroup" | 2, 3 | Collapsible group of navigation items |
LINK | "link" | 2, 3, 4 | Direct 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:
| Value | Description | Use Case |
|---|---|---|
"PUBLIC" | Available to all users including unauthenticated | Public-facing features |
"EXTERNAL" | Available to authenticated external users | Partner/customer portals |
"INTERNAL" | Only for internal authenticated users | Admin and internal tools |
Child Node Properties
Level 2 Nodes (Direct Children of APP_NODE)
Level 2 nodes can be either LINK or MENU_GROUP:
LINK Node Properties (Level 2)
| Property | Type | Required | Description | Validation |
|---|---|---|---|---|
key | string | Yes | Unique identifier within siblings | Lowercase token only |
name | string | Yes | Display text in navigation | Any string |
nodeType | string | Yes | Must be "link" | Exact value: "link" |
link | string | Yes | URL path for navigation | Any string |
description | string | No | Tooltip or additional info | Any 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_keyis the app's key propertyscreen_routeis the screen'sroutefield from the screen configuration/listis 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"
MENU_GROUP Node Properties (Level 2)
| Property | Type | Required | Description | Validation |
|---|---|---|---|---|
key | string | Yes | Unique identifier within siblings | Lowercase token only |
name | string | Yes | Display text for group header | Any string |
nodeType | string | Yes | Must be "menuGroup" | Exact value: "menuGroup" |
children | array | Conditional | Required when nodeType is MENU_GROUP | Array of Level 3 nodes |
description | string | No | Not used for MENU_GROUP | Any string (ignored) |
Level 3 Nodes (Children of MENU_GROUP)
Level 3 nodes can also be either LINK or MENU_GROUP:
LINK Node Properties (Level 3)
Same structure as Level 2 LINK nodes:
| Property | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier within siblings |
name | string | Yes | Display text in navigation |
nodeType | string | Yes | Must be "link" |
link | string | Yes | URL path for navigation |
description | string | No | Tooltip or additional info |
MENU_GROUP Node Properties (Level 3)
| Property | Type | Required | Description | Validation |
|---|---|---|---|---|
key | string | Yes | Unique identifier within siblings | Lowercase token only |
name | string | Yes | Display text for group header | Any string |
nodeType | string | Yes | Must be "menuGroup" | Exact value: "menuGroup" |
children | array | Conditional | Required when nodeType is MENU_GROUP | Array 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.
| Property | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier within siblings |
name | string | Yes | Display text in navigation |
nodeType | string | Yes | Must be "link" |
link | string | Yes | URL path for navigation |
description | string | No | Tooltip 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
linkproperty: Required whennodeTypeis"link", not allowed for other typeschildrenproperty: Required whennodeTypeis"menuGroup", not allowed for"link"descriptionproperty: 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
SimpleCrud Screen Links
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
routefields typically use hyphens (e.g.,customer-order,service-offer) - Screen
keyfields typically use underscores (e.g.,customer_order,service_offer) - App links must use the route value, not the key value
- Always append
/listto 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"
}
]
}