Portal Community

Traditional Pages vs. App Studio's Model

In a traditional web application, a "page" is an HTML document with a URL. Each page is coded separately, with its own markup, styles, and scripts. Navigation sends the browser to a new document.

App Studio takes a different approach. The entire application is a single-page application (SPA) with a structured content model:

Traditional Web AppApp Studio
HTML files for each pageAppPage records stored in the app definition
CSS and layout per pagePane layout configured per breakpoint in the builder
JavaScript per pageWidget actions and optional sandboxed Custom JS
Backend routes for each pageURL routing configured declaratively per AppPage
Navigation = browser loadNavigation = content area swap within the SPA shell
Deploy = publish filesDeploy = publish app definition (JSON stored in service layer)

The App Hierarchy

Every App Studio application is structured as a four-level hierarchy:

1

App

The top-level container. Holds global settings: name, icon, permissions (which roles can access the app), navigation configuration, and published status.

2

AppPage

A named content area with a URL route pattern (e.g., /leads/:id). This is what the sidebar navigation item points to. An App can have many AppPages.

3

Pane

A layout container within an AppPage. Each Pane has its own grid or flex layout, configurable per breakpoint (Desktop/Tablet/Mobile). Panes organize the visual structure of a page.

4

Widget

A placed UI component inside a Pane. Widgets have a type (DataGrid, Chart, Form, Button, etc.), configuration properties, data bindings, and actions. A Pane can hold many Widgets.

What This Means in Practice

No File System Artifacts

When you build an App Studio application, you do not create HTML files, TypeScript components, or CSS stylesheets. Everything is stored as structured configuration in the App Studio data model, persisted through AIExtension.Service.

Content Areas, Not Page Loads

When a user navigates from the "Leads List" AppPage to the "Lead Detail" AppPage, the browser URL changes (e.g., from /app/crm/leads to /app/crm/leads/123) but the SPA shell does not reload. The app tree swaps the active AppPage's Panes and Widgets into the content area — instant navigation, no server round-trips for the shell.

Modals Are Content Areas Too

Modal dialogs in App Studio are not separate HTML dialogs — they reference a Pane configured as a modal. The open-modal action loads that Pane's Widgets into an overlay layer. This means modal content is just as configurable as any other Pane.

Common Misconception

App Studio "pages" are not HTML pages. The URL changes when navigating between AppPages, but there is no server round-trip to load a new document. If you inspect the network tab, you will see data API calls — not page loads.

URL Routing in the No-Pages Model

Each AppPage has a URL route pattern. URL parameters in the route become app variables automatically:

// AppPage: Lead Detail
// Route: /app/crm/leads/:id

// In any widget on this AppPage, use:
{{ route.id }}   // resolves to the actual :id from the URL

// Example: DataGrid bound to entity id
dataSource: "GetLeadById"
params: { id: "{{ route.id }}" }

This makes deep linking work naturally. A user can share /app/crm/leads/456 with a colleague — the app will load and navigate directly to lead 456's detail AppPage.

App Definition as Data

Because the app is configuration (not code), it can be exported as a JSON bundle, version-controlled, and imported into another tenant or environment. This is the foundation of App Studio's environment promotion workflow (dev → staging → production).

// Simplified app definition JSON
{
  "appId": "crm-app",
  "name": "CRM Portal",
  "pages": [
    {
      "pageId": "leads-list",
      "title": "Leads",
      "route": "/leads",
      "panes": [
        {
          "paneId": "main",
          "layout": { "type": "grid", "columns": 12 },
          "widgets": [
            {
              "widgetId": "leads-grid",
              "type": "DataGrid",
              "config": {
                "dataSource": "GetLeads",
                "columns": ["name", "status", "owner"]
              }
            }
          ]
        }
      ]
    }
  ]
}