Custom Pages
Custom Pages
Current version: 0.242.040
Implemented in version: 0.242.023
Example pages added in version: 0.242.024
Restart acknowledgement implemented in version: 0.242.025
File list editor implemented in version: 0.242.026
Admin access and .html alias implemented in version: 0.242.027
Single-source developer guide modal implemented in version: 0.242.028
In-app canonical guide source implemented in version: 0.242.029
Large menu handling implemented in version: 0.242.030
Drawer menu threshold implemented in version: 0.242.031
Drawer trigger handoff implemented in version: 0.242.032
Static asset video example implemented in version: 0.242.033
Literal role enforcement implemented in version: 0.242.034
Admin-as-User role hierarchy implemented in version: 0.242.035
Request Access example and access levels implemented in version: 0.242.036
Request Access guidance modal and duplicate slug checks implemented in version: 0.242.037
Authenticated custom page navigation fix implemented in version: 0.242.040
Overview
Custom Pages let app teams that deploy SimpleChat add trusted, deployment-time pages under the /custom namespace without changing the core application route code. The feature is controlled by the Admin Settings toggle enable_custom_pages and fails closed when disabled.
Dependencies
- Flask host routes in
application/single_app/route_custom_pages.py - Cosmos DB metadata container
custom_pages, configured inapplication/single_app/config.py - Static file folders under
application/single_app/custom_pages/:assets,css,html,js,json, andpython - Admin Settings metadata designer in
application/single_app/templates/admin_settings.html
Technical Specifications
Custom Pages uses always-registered host routes and request-time dispatch. Static page metadata is stored in Cosmos DB so simple page definitions persist across container lifecycle events. Python-backed pages are trusted code shipped with the deployed artifact and discovered from custom_pages/python using CustomPageExtension subclasses or the @custom_page decorator.
The host routes are:
/custom/<slug>for page rendering/custom/<slug>.htmlas a familiar static-page alias for page rendering/custom/assets/<slug>/<folder>/<path:filename>for declared static assets/api/custom/<slug>/<path:operation>for Python-backed custom page operations/api/admin/custom-pagesfor Admin Settings metadata management
Static pages are rendered through custom_page_shell.html after the host route applies @login_required and @user_required. Static HTML content is treated as trusted deployment-time app-team content and is not end-user supplied.
Configuration Options
enable_custom_pages: Enables or disables all/custompage and asset routes.custom_pages_menu_name: Controls the navigation label when pages are grouped.custom_pages_force_menu: Forces custom pages to display as a menu even when only one or two pages exist.
The feature version was updated in application/single_app/config.py from 0.242.022 to 0.242.023.
The example page set was added with version 0.242.024.
Version 0.242.025 added a restart acknowledgement modal for first-time enablement and records the acknowledgement to activity_logs as custom_pages_enabled_acknowledged.
Version 0.242.026 replaced comma-separated CSS, JavaScript, asset, and JSON text fields with explicit add/remove file list editors.
Version 0.242.027 added /custom/<slug>.html as a compatibility alias and improved modal spacing for deployed files and publishing toggles.
Version 0.242.028 added a single-source developer guide modal.
Version 0.242.029 moved the canonical guide source into application/single_app/docs/how-to/custom_pages.md so non-container deployments and ACR-built containers receive the guide with the app artifact. The docs how-to page now links to that in-app source instead of duplicating the guide.
Version 0.242.030 clarified forced menu behavior with page count badges and bounded scrolling in sidebar, top navigation, and mobile navigation menus.
Version 0.242.031 changed large custom page sets to use a dedicated drawer when more than five pages are available. One or two pages can stay inline unless the menu is forced, three to five pages use a compact grouped menu, and six or more pages open the drawer with its own scroll area.
Version 0.242.032 added a shared drawer trigger handler so mobile navigation closes before the Custom Pages drawer opens.
Version 0.242.033 added cat.mp4 as a declared asset in the static example page and renders it with an HTML video viewer.
Version 0.242.034 removed the admin bypass from custom page authorization. When a page has Allowed Roles configured, every user must have at least one exact matching role in the current session.
Version 0.242.035 treats Admin as satisfying the base User role while still requiring exact matches for all other page-specific roles.
Version 0.242.036 added access levels, a repo-shipped Request Access static page, a one-click Admin Settings action to create its metadata, and an access-denied home-page button for signed-in users without app roles. The earlier learning examples were moved out of live custom pages into application/single_app/docs/how-to/custom_pages_examples/.
Version 0.242.037 added post-create guidance for editing the Request Access email address, disables the one-click Request Access helper once request-access exists, and prevents duplicate slugs in both the Admin Settings modal and create API.
Version 0.242.040 changed navigation rendering so Custom Pages appears for any signed-in user only when custom_pages_nav contains at least one enabled, visible, authorized page. This allows access_level=authenticated pages such as Request Access to render for signed-in users without User or Admin, while hiding the pane when no custom page is available.
Usage Instructions
Admins can open Admin Settings > Custom Pages to enable the feature and create metadata for simple static pages. Metadata references files already deployed under application/single_app/custom_pages/html, css, js, assets, and json.
For CSS, JavaScript, asset, and JSON references, admins add one file at a time using the Add button in the metadata designer. The backend still persists those values as arrays in the custom_pages Cosmos container.
When Allowed Roles is blank, any signed-in app user can access the custom page. When Allowed Roles has values, users must have at least one matching role in the current session. Admin satisfies User, but Admin does not satisfy arbitrary custom roles. Unknown role names do not grant access.
Access Level controls the base gate before page-specific roles are checked. App users only requires User or Admin. Any signed-in user requires login but does not require the base app role, and is intended for bootstrap pages such as Request Access.
When enabled, authorized pages appear in top and left navigation between Chat and the Support/External Links area. When disabled, /custom routes return Not Found before metadata lookup, file access, or Python extension dispatch.
When Custom Pages is enabled but no custom page metadata or Python-backed pages are available, the navigation still shows the Custom Pages section with a No custom pages registered empty state.
When an admin enables Custom Pages from a disabled state, the Admin Settings UI displays a restart acknowledgement modal. The setting is only accepted after acknowledgement, and the successful save writes an activity log record because App Service restart is required before newly deployed custom page files and Python-backed pages are fully active.
Example Pages
The repository includes three deployment-time examples:
example-simple.html: A minimal HTML fragment that only needs metadata in the Admin Settings designer.example-static.html,example-static.css, andexample-static.js: A static page with page-specific CSS and JavaScript.example_python_dashboard.py: A Python-backed page that rendersexample-python-dashboard.htmlas a Jinja template and exposes/api/custom/example-python-dashboard/statusthrough the host dispatcher.
The Python-backed example is discovered from code, so it should not be created in the metadata designer. Static examples are stored in Cosmos through the designer.
Testing and Validation
Functional coverage was added in functional_tests/test_custom_pages_wiring.py. It validates version/configuration wiring, protected route registration, Admin Settings UI wiring, navigation integration, and the trusted HTML rendering boundary without requiring live Cosmos DB connectivity.
Known limitations:
- The Admin Settings designer manages metadata only; app teams still deploy the referenced files as part of the application artifact.
- Python-backed pages are trusted deployment-time extensions, not arbitrary runtime code uploaded by users.