Table of Contents

Pages & Templates

Every operator-facing page in the React shell is a data row in the FrontEnd DB. The page itself is not source code — it's a template (an ordered list of component slots) bound to per-slot configuration JSON. Adding, removing, or rearranging pages is a database operation, not a release.

This page covers:

  • The three-layer data model (Component → Template → Page).
  • The URL structure the React shell uses.
  • The four workflows for editing the data model.
  • The full set of MIB3UX_PAGE_CONFIGURATIONS keys.

Related docs.
Menus — pointing menu entries at pages.
Component Authoring — writing the C# + React halves of a custom widget that slots into a template.
System Overview — RenderV2 — what happens between the React shell's render request and the JSON response.

Conceptual model

Component               ┐                                              
  ComponentKey          │     Reusable widget, identified by a string  
  AssemblyName          │     [ComponentType("...")] on its C# class.  
  ClassName             │                                              
                        ┘                                              

Template                ┐                                              
  TemplateKey           │     Reusable page layout: an ordered list    
  HasAnchorMenu         │     of components.                           
  List<Component>       │                                              
                        ┘                                              

Page                    ┐                                              
  PageKey               │     A concrete page — one specific template  
  Template              │     bound to per-page settings + per-slot    
  PageSettings          │     component configuration.                 
  PerSlotConfiguration  │                                              
                        ┘                                              

Three distinct identifier strings travel through this model — keep them straight:

Identifier Scope Example
ComponentKey Identifies the widget class (one per registered C# type). mib_default_listcomponent, my_custom_widget
TemplateComponentKey Identifies one slot in one template. Unique within a template. articles_template_001
PageKey Identifies a page in URLs and menus. articles_edit, articles_list

Backing tables in the FrontEnd DB:

Table Holds
MIB3UX_COMPONENTS One row per registered widget class (ComponentKey).
MIB3UX_TEMPLATES One row per template (TemplateKey).
MIB3UX_TEMPLATE_COMPONENTS Join rows: which widget instances belong to which templates, in what Order.
MIB3UX_PAGES One row per page, binding a PageKey to one TemplateKey.
MIB3UX_PAGE_CONFIGURATIONS Page-level settings as (PAGE_KEY, KEY) → VALUE rows.
MIB3UX_PAGE_COMPONENT_CONFIGURATIONS Per-page-per-slot widget configuration as (PAGE_KEY, TEMPLATE_COMPONENT_KEY, KEY) → VALUE rows.

URL structure

The React shell serves all pages from a single SPA host. URLs are:

Pattern Mode Example
~/app/<pageKey> List mode (no ids) ~/app/articles_list
~/app/<pageKey>/<id> Edit mode (single record) ~/app/articles_edit/12345
~/app/<pageKey>/<id1>,<id2>,… Bulk-edit mode (multiple records) ~/app/articles_edit/12345,12346,12350

The shell parses the URL, issues GET /api/v2/display/<pageKey>/<id> against the FrontEnd Server, receives {schema, data}, and dispatches each component schema.components[i] to either a built-in widget or a federated remote — see Overview for the full flow.

Editing the data model

Four workflows in roughly increasing order of formality:

1. Development Tool UI

An admin UI inside the FrontEnd Server's local-development host lets you create / edit pages, drag widgets into templates, and edit configuration JSON for each slot interactively. Best for prototyping and for one-off changes during development.

See Local Development. Production environments typically don't expose the dev tool UI.

2. SQL migrations (production path)

For production, ship the rows as SQL migrations against the FrontEnd DB through mibmigrator. A typical dev cycle:

  1. Use the dev tool to lay out the page interactively.
  2. Export the resulting INSERT/UPDATE statements (or hand-write a migration mirroring the rows).
  3. Commit the migration into the customer's customisation repository (<Customer>.Mib3.MibDatabaseMigrations or equivalent).
  4. Apply the migration in the target environment with mibmigrator.

See Framework — Migrations for the migration tooling.

3. Customer customisation seed code

A customer's C# customisation repo typically wraps the migration boilerplate in a Seed helper:

seed.AddOrUpdatePage("articles_edit", "articles_template");

seed.AddOrUpdateTemplateComponent(new UxTemplateComponent
{
    ComponentKey         = "mib_default_formcomponent",
    TemplateComponentKey = "articles_template_main_form",
    Order                = 10,
}, "articles_template");

seed.AddOrUpdatePageComponentConfiguration(
    "mib_default_formcomponent",
    "articles_template_main_form",
    new Dictionary<ComponentConfigurationKey, string>
    {
        { ComponentConfigurationKey.MediaType, "ARTICLES" },
        { ComponentConfigurationKey.Title,     "Article" },
        { ComponentConfigurationKey.Readonly,  "false"   },
    },
    "articles_edit");

Browse the customer customisation's src/Database/Migrations/ for examples of every operation (add, update, remove, reorder).

4. Direct SQL (read-only or emergency only)

For investigation, query the tables directly:

-- All pages
SELECT PAGE_KEY, TEMPLATE_KEY FROM MIB3UX_PAGES;

-- All slots on one page's template
SELECT TC.TEMPLATE_COMPONENT_KEY, C.COMPONENT_KEY, TC.[ORDER]
FROM   MIB3UX_TEMPLATE_COMPONENTS TC
JOIN   MIB3UX_COMPONENTS          C  ON TC.COMPONENT_KEY = C.COMPONENT_KEY
JOIN   MIB3UX_PAGES               P  ON P.TEMPLATE_KEY   = TC.TEMPLATE_KEY
WHERE  P.PAGE_KEY = 'articles_edit'
ORDER  BY TC.[ORDER];

-- Per-slot configuration on one page
SELECT TEMPLATE_COMPONENT_KEY, [KEY], VALUE
FROM   MIB3UX_PAGE_COMPONENT_CONFIGURATIONS
WHERE  PAGE_KEY = 'articles_edit'
ORDER  BY TEMPLATE_COMPONENT_KEY, [KEY];

-- Page-level settings
SELECT [KEY], VALUE
FROM   MIB3UX_PAGE_CONFIGURATIONS
WHERE  PAGE_KEY = 'articles_edit';

Direct UPDATEs against these tables work but never do this in production — the rows must be expressed as migrations so other environments stay in sync.

Page-level settings — MIB3UX_PAGE_CONFIGURATIONS

Each row in this table is a single (PAGE_KEY, KEY) → VALUE setting. A page can have many entries, one per setting. Boolean values may be stored as "true"/"false" strings; integers and other primitives are stringified accordingly.

ALLOW_AUDIT_VIEW

Enables the audit panel for the page. true / false.

When true, a button in the top-right opens the audit drawer (Audit Trail). The button is only useful when the EditHistory Microservice is configured (UseEditHistoryMicroService=true on the BFF) — otherwise the drawer opens empty.

RETURN_URL

The URL the page's "Return" button navigates to. Used to override the default of "the previous page in browser history".

Important

Mandatory for edit pages: when a user lands on an edit page directly (e.g. via a deep link from email or a bookmark), the browser has no history to "go back" to. Without RETURN_URL the Return button breaks.

Example:

RETURN_URL = ~/app/articles_list

RETURN_AT_SAVE

true / false. When true, the page navigates to RETURN_URL (or the previous page) immediately after a successful save, rather than refreshing the edit page with the saved record. Useful for "create then return to list" flows.

RESTORE_STATE

true / false. When true, the shell restores the page's client-side state (scroll position, list filters, pagination, sort, selection) when the user navigates away and comes back.

Implementation: state is keyed by PageKey in sessionStorage. Query-string parameters are not part of the saved state — they have to be reproduced from the page's own configuration.

true / false. Default false. When true, the Clone with related option in the page's action menu is hidden; only Clone (without related) remains. Use when the cost of duplicating child records is too high to be a safe single click.

ALERT_DELETE_MESSAGE

Custom text shown in the delete-confirmation modal. Accepts plain text or a {DICT:Section/Key} translation marker (see Localization).

ALERT_DELETE_MESSAGE = {DICT:Articles/DeleteConfirmText}

If unset, the shell falls back to a default message.

CUSTOM_CONFIGURATION

A JSON blob of free-form properties merged into the page-render response and exposed to every component on the page. The shell puts the parsed object on response.configuration — custom widgets read it via the React useContentContext() hook or by reading the prop their parent passes down.

Use for:

  • Customer-wide feature flags scoped to a specific page.
  • Per-page UX tweaks that don't fit any standard key.
  • One-off values consumed by a custom widget.
{
  "showExperimentalView": true,
  "maxBulkEditRows":      50,
  "defaultLanguage":      "en-us",
  "feature":              { "enablePreview": false }
}

Stored as a JSON string; parsed on every render. Stick to flat structures where possible — nested objects work but make debugging harder.

SORTER

Boolean. When true, the BFF includes a sorter flag in the render response and the shell shows the tab-sorter UI anchored to the gear icon in the top-right of an edit page. Each tab row in the sorter has a drag handle on the left and a visibility toggle (eye) on the right — drag to reorder, click the eye to hide a tab from the user's view:

Tab sorter panel on an edit page, gated by SORTER=true

Pairs with the useSorter() shared hook — custom widgets that decorate a sorted layout can read the current sort order from the shell.

Per-slot configuration — MIB3UX_PAGE_COMPONENT_CONFIGURATIONS

Each row binds one configuration key on one slot on one page to a value:

(PAGE_KEY, TEMPLATE_COMPONENT_KEY, KEY) → VALUE

The available keys vary by component type — see the per-component docs:

Some keys are shared across most components (e.g. TITLE, MEDIATYPE, READONLY, ALLOW_BULK_EDIT); each component doc lists its full set.

Inspecting a configured page

Three practical ways to see how a page is set up:

A. Network panel

Open the page in the React shell, open DevTools → Network, and inspect the GET /api/v2/display/<pageKey>/<id> response. The body is one JSON object with the whole rendered page:

{
  "pageKey":       "articles_edit",
  "title":         "Articles",
  "configuration": { /* page-level settings, merged with CUSTOM_CONFIGURATION */ },
  "schema":        { "components": [ /* one per slot, schema only */ ] },
  "data":          { "components": [ /* one per slot, values only */ ] }
}

This is the canonical "what does the operator actually see" — what the shell receives is what the shell renders.

B. Direct SQL

SELECT [KEY], VALUE
FROM   MIB3UX_PAGE_CONFIGURATIONS
WHERE  PAGE_KEY = 'articles_edit';

SELECT TEMPLATE_COMPONENT_KEY, [KEY], VALUE
FROM   MIB3UX_PAGE_COMPONENT_CONFIGURATIONS
WHERE  PAGE_KEY = 'articles_edit'
ORDER  BY TEMPLATE_COMPONENT_KEY, [KEY];

C. Development Tool

The dev tool shows page + slot configuration in a structured editor — useful for non-developers. See Local Development.

Common gotchas

  • RETURN_URL unset on an edit page — Return button breaks for deep-link arrivals. Always set it.
  • SORTER enabled but no operator-facing benefit — tab sorter only helps when an edit page has many sibling components. Don't enable it on simple form-only pages.
  • CUSTOM_CONFIGURATION blob too deep — nested objects three levels down become invisible during debugging. Flatten where you can.
  • A new page key but no menu entry — the page works at its URL but operators can't navigate to it. Add a row to MIB3UX_MENU — see Menus.

See also