Browse Source

docs: expand low-code documentation

pull/25780/head
SALİH ÖZKARA 1 month ago
parent
commit
05b34be7f6
  1. 66
      docs/en/docs-nav.json
  2. 183
      docs/en/low-code/dashboards.md
  3. 41
      docs/en/low-code/designer.md
  4. 83
      docs/en/low-code/health.md
  5. BIN
      docs/en/low-code/images/designer-entity.png
  6. BIN
      docs/en/low-code/images/designer-overview.png
  7. BIN
      docs/en/low-code/images/designer-properties.png
  8. BIN
      docs/en/low-code/images/runtime-create-form.png
  9. BIN
      docs/en/low-code/images/runtime-dashboard.png
  10. BIN
      docs/en/low-code/images/runtime-data-grid.png
  11. BIN
      docs/en/low-code/images/studio-low-code-step.png
  12. 35
      docs/en/low-code/index.md
  13. 126
      docs/en/low-code/mcp.md
  14. 74
      docs/en/low-code/model-json.md
  15. 123
      docs/en/low-code/page-groups.md
  16. 9
      docs/en/low-code/react-runtime.md

66
docs/en/docs-nav.json

@ -469,6 +469,72 @@
"path": "suite/how-to-uninstall.md"
}
]
},
{
"text": "Low-Code System",
"items": [
{
"text": "Overview",
"path": "low-code",
"isIndex": true
},
{
"text": "Low-Code Designer",
"path": "low-code/designer.md"
},
{
"text": "React Runtime",
"path": "low-code/react-runtime.md"
},
{
"text": "Health",
"path": "low-code/health.md"
},
{
"text": "Dashboards",
"path": "low-code/dashboards.md"
},
{
"text": "Page Groups",
"path": "low-code/page-groups.md"
},
{
"text": "MCP Integration",
"path": "low-code/mcp.md"
},
{
"text": "Attributes & Fluent API",
"path": "low-code/fluent-api.md"
},
{
"text": "Model Descriptor Files",
"path": "low-code/model-json.md"
},
{
"text": "Reference Entities",
"path": "low-code/reference-entities.md"
},
{
"text": "Foreign Access",
"path": "low-code/foreign-access.md"
},
{
"text": "Interceptors",
"path": "low-code/interceptors.md"
},
{
"text": "Custom Endpoints",
"path": "low-code/custom-endpoints.md"
},
{
"text": "Script Actions",
"path": "low-code/script-actions.md"
},
{
"text": "Scripting API",
"path": "low-code/scripting-api.md"
}
]
}
]
},

183
docs/en/low-code/dashboards.md

@ -0,0 +1,183 @@
```json
//[doc-seo]
{
"Description": "Define ABP Low-Code dashboard pages, visualizations, filters, layout, permissions, and React runtime data flow."
}
```
# Dashboards
Dashboards are low-code pages that render charts, lists, and number widgets from low-code entity data. A dashboard is still a page, so it uses the normal page name, title, icon, order, group, and permission model, but its page type is `dashboard` and its page definition carries a nested `dashboard` payload.
The runtime below was generated from a low-code dashboard page definition:
![Generated React dashboard page](images/runtime-dashboard.png)
## Dashboard Pages
Dashboard pages live in the normal page descriptor collection. In source-controlled models, a dashboard page still belongs under `pages/`, not a separate dashboard folder.
Typical page-level fields include:
* `name`
* `title`
* `icon`
* `type: "dashboard"`
* `group`
* `order`
* `permissionConfig`
* `dashboard`
Runtime routes use the same dynamic page convention:
```text
/dynamic/<page-name>
```
## Layout Model
The stored dashboard descriptor uses a **flat visualization list**. Each visualization defines its own placement:
* `row`: zero-based visual row index
* `order`: order within the row
* `width`: current dashboard grid width, typically `1` or `2`
At runtime, the React UI groups those flat visualizations into rendered rows. This is similar to form layouts: storage stays flat, rendering derives the grouped structure.
```json
{
"name": "sales-dashboard",
"title": "Sales Dashboard",
"type": "dashboard",
"group": "analytics",
"dashboard": {
"description": "Operational sales view",
"globalFilters": [
{ "type": "dateRange" }
],
"visualizations": [
{
"name": "sales-by-status",
"type": "chart",
"title": "Sales by Status",
"row": 0,
"order": 0,
"width": 2,
"entityName": "Acme.Sales.Order",
"chart": {
"chartType": "bar",
"xAxis": { "property": "Status" },
"yAxis": [
{ "aggregation": "count", "label": "Orders" }
]
}
},
{
"name": "totals",
"type": "numberContainer",
"title": "Totals",
"row": 1,
"order": 0,
"width": 2,
"numberContainer": {
"items": [
{
"name": "order-count",
"title": "Order Count",
"entityName": "Acme.Sales.Order",
"aggregation": "count",
"format": "number"
}
]
}
}
]
}
}
```
## Visualization Types
The current dashboard visualization types are:
* `chart`
* `list`
* `numberContainer`
### Chart
Chart visualizations define:
* `chartType`: `bar`, `line`, `pie`, or `donut`
* `xAxis`
* one or more `yAxis` aggregation series
* optional `maxItems`
* optional `showRecordCount`
* optional bar orientation
### List
List visualizations define:
* `fields`
* optional `sortBy`
* `maxRows`
* `rowHeight`
* optional `colorBy`
### Number Container
Number containers hold one or more number items. Each item can define:
* `aggregation`
* `aggregationProperty`
* `format`
* `color`
* entity-specific filters and global date filter linkage
* click-through behavior
## Filters and Interactivity
Dashboards support three filter layers:
* `globalFilters` for page-level controls such as date range
* visualization-level `filter` for fixed query constraints
* visualization `userFilters` for interactive filtering exposed to the runtime user
Other useful dashboard interaction fields include:
* `globalDateFilterProperty`
* `showDescriptionAsTooltip`
* `clickToSeeRecords`
These options let a dashboard stay compact while still allowing drill-down behavior in the runtime.
## Runtime Shape
The runtime definition exposed to React is grouped by rows and items, even though the stored descriptor is flat. The React runtime uses:
* `useDashboardDefinition`
* `useDashboardData`
* `GET /api/low-code/ui/dashboards/{pageName}`
* `POST /api/low-code/dashboards/{pageName}/data`
See [React Runtime](react-runtime.md) for hook-level details and route integration.
## Permissions and Menu Placement
Dashboard pages are read-oriented. Generated dashboard page operations are **view only**, so the usual CRUD operation set does not apply.
Dashboard pages can still:
* appear inside a [Page Group](page-groups.md)
* carry a menu icon and order
* use explicit or generated page permission configuration
Menu placement is handled at the page level through the normal `group` and `order` fields.
## See Also
* [React Runtime](react-runtime.md)
* [Low-Code Designer](designer.md)
* [Page Groups](page-groups.md)
* [Model Descriptor Files](model-json.md)

41
docs/en/low-code/designer.md

@ -58,6 +58,8 @@ For reference entities such as `IdentityUser`, register the entity in the genera
Use **Pages** to expose an entity in the React runtime.
Use [Page Groups](page-groups.md) to organize runtime menu folders and [Dashboards](dashboards.md) when a page type needs dashboard-specific descriptor details.
Pages can define data grid, kanban, calendar, gallery, standalone form, and dashboard experiences. A data grid page can define:
* Title and icon
@ -98,6 +100,37 @@ Forms can contain:
* Conditional rules for hide/show, enable/disable, and set value behavior
* Save actions such as "save and new"
Source-controlled forms still need valid layout placements. If a form shows `No fields in this group`, check the matching descriptor file and make sure the group uses `layout.tabs[].groups[].fields[]` placements whose `fieldId` values match the form field definitions.
For example, this form defines three fields and places those same field IDs into the group layout:
```json
{
"fields": [
{ "id": "name", "label": "Name", "type": "text", "binding": "Name" },
{ "id": "price", "label": "Price", "type": "money", "binding": "Price" },
{ "id": "is-active", "label": "Active", "type": "checkbox", "binding": "IsActive" }
],
"layout": {
"tabs": [
{
"groups": [
{
"fields": [
{ "fieldId": "name", "row": 0, "colSpan": 4 },
{ "fieldId": "price", "row": 1, "colSpan": 2 },
{ "fieldId": "is-active", "row": 1, "colSpan": 2 }
]
}
]
}
]
}
}
```
The designer and runtime read the flat `fields[]` placement list shown above. If placements are missing, or if a placement points to a different ID such as `isActive` instead of `is-active`, the form can load but the group may stay visually empty.
![Form setup](images/designer-forms.png)
## Filters
@ -128,7 +161,13 @@ Endpoint and event handler editors include **Test JavaScript**. Dry-run executio
## Health
Use **Health** before shipping changes. It helps catch missing display properties, invalid relation targets, form/page references, script problems, and other model issues that would otherwise surface at runtime.
Use **Health** before shipping changes. It helps catch missing display properties, invalid relation targets, form/page references, script problems, and other model issues that would otherwise surface at runtime. See [Health](health.md) for the selected-layer snapshot scope and the typical problem classes it helps you review.
## MCP Integration
The Designer and the low-code MCP surface overlap when the selected layer is **Runtime JSON**, but they are not the same editing surface. The Designer can inspect source-controlled and runtime layers, while [MCP Integration](mcp.md) is intentionally runtime-only and targets the database-backed model. Use the Designer when you want interactive editing and visual feedback. Use MCP when an agent or script needs a structured read, validate, apply, and health-check workflow against the runtime model.
After MCP-driven changes, reopen the relevant Designer section or review [Health](health.md) before reporting the model as ready.
## Source Control

83
docs/en/low-code/health.md

@ -0,0 +1,83 @@
```json
//[doc-seo]
{
"Description": "Review the ABP Low-Code model health snapshot to inspect entities, pages, forms, page groups, permissions, and script assets before publishing runtime changes."
}
```
# Health
The **Health** section in the Low-Code Designer is a selected-layer readiness view. It helps you review the resolved low-code model for the currently selected layer before publishing changes or relying on the generated runtime pages.
Health works on the **resolved view of the selected layer**, not on raw JSON files. In practice, this means it shows the selected layer after lower-layer inheritance and descriptor materialization have been applied. It does not silently merge unrelated edits from another layer. To review runtime-only overrides, switch the Designer to **Runtime JSON** or use the runtime-focused [MCP Integration](mcp.md) workflow.
## What Health Reads
The health snapshot is a combined view of the current low-code model in the selected layer. It includes:
* Entities
* Enums
* Page groups
* Pages
* Forms
* Custom permissions
* Effective page permission configuration
* Custom endpoints
* Script event handlers
* Script background jobs
* Script background workers
This makes Health the best place to review cross-descriptor relationships instead of checking each designer tab in isolation.
## What It Helps You Catch
Health is most useful for selected-layer consistency problems that often appear only after several descriptors start referencing each other.
Typical problem classes include:
* Page to form mismatches, where a page references a form that does not exist or belongs to another entity
* Page-type configuration errors, such as a kanban page missing `groupByProperty`, a calendar page using invalid date or time fields, or a gallery page pointing to a non-image property
* Page-group structure problems such as missing parents, circular references, or nesting deeper than the supported depth
* Form layout issues where fields exist but valid placements do not
* Permission and page configuration mismatches that would affect runtime visibility or access
* Script assets that exist in the model but still need review in the broader context of entities, pages, and permissions
Some of these issues are also enforced by runtime validation and mutation rules. Health gives you a selected-layer review point before users discover the problem in the runtime UI.
## Use It When
Use Health in these moments:
* After changing entities, pages, forms, page groups, or permissions in the Designer
* After applying MCP-driven runtime mutations
* Before publishing a set of runtime changes
* After copying or importing source-controlled descriptors into an application
If you automate low-code changes through [MCP Integration](mcp.md), re-read the health snapshot after apply and treat that review as part of the success criteria.
## Health and Source-Controlled Models
Health is not a replacement for source-controlled file validation.
When you edit `_Dynamic/model/**/*.json` directly:
1. Run the generated model file checker:
```bash
dotnet run --project <startup-project> -- --check-lowcode-model-files
```
2. Run the required migration workflow for schema-affecting entity changes.
3. Open the Designer and review **Health** after the model has loaded successfully.
The file checker catches JSON and category-level problems. Health complements it by helping you inspect the resolved model after descriptors, references, permissions, pages, and forms are all combined.
A useful example is form layout drift: a descriptor can still pass `--check-lowcode-model-files` while a source-controlled form renders with no visible fields because its layout placements are stale. Health and runtime preview are the places to catch that class of issue.
## See Also
* [Low-Code Designer](designer.md)
* [MCP Integration](mcp.md)
* [Model Descriptor Files](model-json.md)
* [Dashboards](dashboards.md)
* [Page Groups](page-groups.md)

BIN
docs/en/low-code/images/designer-entity.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/en/low-code/images/designer-overview.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/low-code/images/designer-properties.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/en/low-code/images/runtime-create-form.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/en/low-code/images/runtime-dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/en/low-code/images/runtime-data-grid.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/low-code/images/studio-low-code-step.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

35
docs/en/low-code/index.md

@ -34,9 +34,13 @@ Low-Code runtime UI is currently documented for **React**. The backend model, AP
## How to Enable
The Low-Code System is an optional startup template feature. When creating a new application with [ABP Studio](../studio/index.md), choose a modern React application template and enable **Low-Code System** in the project creation wizard.
The Low-Code System is an optional [ABP Studio](../studio/index.md) feature for modern application templates. In the modern wizard, the **Low-Code System** step is available for layered, single-layer, and modular monolith solutions. The step is skipped for microservice architecture and disabled when MongoDB is selected because runtime-managed tables require EF Core.
ABP Studio creates the required backend module references, dynamic model initializer, EF Core configuration, Admin Console integration, and React runtime wiring.
![ABP Studio Low-Code step](images/studio-low-code-step.png)
Enable **Include Low-Code runtime and designer** to add the low-code runtime, designer APIs, EF Core integration, and, when a React web UI is included, the generated React runtime wiring and dynamic routes.
ABP Studio creates the required backend module references, dynamic model initializer, EF Core configuration, and Admin Console integration. When the solution also includes the React web application, Studio adds the `@volo/abp-react-lowcode` package, dynamic routes, and menu integration for `/dynamic/...` runtime pages.
The generated React project includes:
@ -49,6 +53,15 @@ The generated React project includes:
The host application wires the low-code modules, calls the generated `_Dynamic` initializer, configures EF Core dynamic entities, and seeds the required OpenIddict clients.
Generated solutions keep source-controlled low-code assets under `_Dynamic`:
* Layered and modular monolith solutions: `src/<YourApp>.Domain/_Dynamic/`
* Single-layer solutions: `<YourApp>.Host/_Dynamic/`
The `_Dynamic` folder includes the generated initializer, a `model/` directory that the runtime scans, and a `model-examples/` directory with sample descriptors that stay inactive until copied into `model/`.
Copying files from `model-examples/` into `model/` activates the descriptors, but persisted entity changes still follow the normal source-controlled migration flow. Run the generated database or low-code migration task before expecting `/dynamic/...` pages or `/api/low-code/pages/.../data` endpoints to read and write the backing table safely.
## Run the Application
After ABP Studio creates the solution, use **Solution Runner** to run the backend host and the React application. Run the database migration task before opening the runtime pages.
@ -69,6 +82,16 @@ Open generated runtime pages after the React application is running:
http://localhost:<react-port>/dynamic/<page-name>
```
If a copied sample page returns `403`, grant the generated low-code permissions to the role or user you are testing with through the standard ABP permission management UI.
If you edit descriptor JSON files manually, validate them before running migrations or publishing changes:
```bash
dotnet run --project <startup-project> -- --check-lowcode-model-files
```
The generated startup project accepts `--model-directory <path-to-_Dynamic/model>` when you want to validate a specific folder instead of the configured source assemblies.
## Designer Workflow
The designer is the day-to-day entry point.
@ -146,6 +169,10 @@ The designer stores and reads the same descriptor metadata described in the refe
|-------|------------|
| [Designer](designer.md) | Admin Console tabs, entity/page/form setup, permissions, and health |
| [React Runtime](react-runtime.md) | React package wiring, routes, menu items, filters, forms, and export |
| [Health](health.md) | Selected-layer readiness review across entities, pages, forms, page groups, permissions, and scripts |
| [Dashboards](dashboards.md) | Dashboard page descriptors, visualization layout, filters, and runtime data flow |
| [Page Groups](page-groups.md) | Dynamic menu folders, nesting, ordering, and page grouping |
| [MCP Integration](mcp.md) | Runtime designer automation with structured reads, validation, apply, and health review |
| [Attributes & Fluent API](fluent-api.md) | Source-controlled C# metadata and runtime overrides |
| [Model Descriptor Files](model-json.md) | JSON descriptor files and public descriptor schemas used by the designer and runtime |
| [Reference Entities](reference-entities.md) | Lookups to existing entities such as Identity users |
@ -170,4 +197,8 @@ The generated pages are powered by these services:
* [Low-Code Designer](designer.md)
* [React Runtime](react-runtime.md)
* [Health](health.md)
* [Dashboards](dashboards.md)
* [Page Groups](page-groups.md)
* [MCP Integration](mcp.md)
* [Model Descriptor Files](model-json.md)

126
docs/en/low-code/mcp.md

@ -0,0 +1,126 @@
```json
//[doc-seo]
{
"Description": "Use the ABP Low-Code Designer MCP integration to inspect, validate, and apply structured runtime mutations to the database-backed low-code model."
}
```
# MCP Integration
> **Preview:** The low-code MCP surface is part of the preview Low-Code System. Tool names, mutation metadata, and validation advice may change before general availability.
The low-code designer exposes a Model Context Protocol (MCP) surface for automation. It is intended for agents and scripted tooling that need to inspect or mutate the same **runtime database-backed** model that the Designer edits in the **Runtime JSON** layer.
## Scope
The MCP surface is **runtime-only**:
* It always works against the database-backed runtime model.
* It does not choose between layers at call time.
* It is not the right surface for source-controlled `_Dynamic/model/**/*.json` files.
Use [Model Descriptor Files](model-json.md) when you need source-controlled descriptors. Use MCP when an agent or automation needs safe, structured changes to the runtime model that the Designer can immediately show.
## Mental Model
MCP is not a raw JSON document editor. It is a semantic mutation layer over low-code concepts such as:
* Entities and properties
* Pages, page groups, and dashboards
* Forms and form layout
* Permissions
* Custom endpoints and script actions
The MCP surface includes read/query capabilities, mutation metadata, dry-run validation, a single mutation apply path, and a health snapshot. The normal workflow is:
1. Read the current runtime model state for the item you want to change.
2. Fetch the latest mutation metadata and keep its `concurrencyStamp`.
3. Build a small ordered mutation batch that touches only the changed paths.
4. Dry-run the batch with validation.
5. Apply the batch.
6. Re-read the changed item and review [Health](health.md).
## Concurrency and Validation
Every runtime write depends on the latest `concurrencyStamp`. If another write changes the runtime model first, your apply attempt becomes stale and must be rebuilt from the refreshed model state.
Validation happens before apply and returns structured feedback instead of requiring a client to guess from generic failures. The validation layer checks:
* Stale concurrency stamps
* Target path syntax
* Missing required input such as `data` or move destinations
* Primitive-only `Set` data
* Final descriptor shape after the batch is simulated
* Entity property type and foreign key rules
* Form layout consistency
* Dashboard layout consistency
* Runtime override rules for descriptors inherited from lower layers
Treat validation as part of the normal write flow, not as an optional extra.
## Mutation Model
Runtime writes are intentionally narrow:
* `Add` creates a keyed descriptor or collection member.
* `Set` changes a scalar value or a supported primitive list.
* `Remove` deletes the semantic target.
* `Move` relocates an existing node without resending the whole object.
Important behavior:
* `Set` data must stay primitive: string, number, boolean, `null`, or a supported primitive string list.
* Do not replace whole descriptor trees when only one field changed.
* Keep batches small and semantic. Send changed paths, not full model payloads.
* If a mapped entity or property is removed with drop behavior, the runtime model can also remove the physical table or column in the runtime layer.
## Runtime Override and Schema Rules
Runtime MCP writes can extend descriptors that originate from lower layers, but they do not have unlimited control over inherited schema.
For entity properties inherited from a lower layer, runtime edits are intentionally restricted. Typical runtime-safe changes are display labels and runtime-owned validators. Inherited storage details such as type, database mapping, default value, required or unique behavior, foreign key settings, and file or image storage options remain immutable in runtime.
This separation matches the low-code layer model:
* Runtime database-backed changes use **direct** schema mutation.
* Source-controlled JSON layer changes use the normal **migration** workflow.
If you are working in `_Dynamic/model`, MCP is the wrong tool. Use the source-controlled descriptor flow and migrations instead.
## Layout and Type Conventions
Several conventions matter when MCP clients generate mutations:
* Use canonical lowercase entity property types: `string`, `int`, `long`, `decimal`, `datetime`, `boolean`, `guid`, `enum`, `date`, `time`, `file`, `image`, `money`.
* Use canonical lowercase form field types: `text`, `textarea`, `number`, `checkbox`, `date`, `datetime`, `select`, `lookup`, `guid`, `computed`, `time`, `file`, `image`, `money`.
* Form layout is flat and id-keyed under `layout.tabs[].groups[].fields[]`. Each placement carries `row`, `colSpan`, and optional `colStart`.
* Dashboard layout is flat and name-keyed under `dashboard.visualizations[]`. Each visualization carries `row`, `order`, and `width`.
* Page and page-group icons are CSS class strings, not URLs or image file paths.
* For foreign access, `foreignKey.access` values are `none`, `view`, or `edit`.
These rules matter because validation and runtime rendering assume them. For example, a form can define fields correctly but still render an empty group if no valid placements point to those field IDs.
## Designer and MCP Together
Use the Designer when you want:
* Interactive editing
* Visual context for entities, pages, forms, and permissions
* Manual review before publishing
Use MCP when you want:
* Agent-driven or scripted changes
* Repeatable runtime mutation workflows
* Structured validation before write
* Safe, incremental edits instead of raw JSON replacement
The two surfaces are complementary. A common workflow is to inspect or prototype in the Designer, automate a repeatable mutation flow through MCP, then reopen the Designer and [Health](health.md) to confirm the result.
## See Also
* [Low-Code Designer](designer.md)
* [Health](health.md)
* [Dashboards](dashboards.md)
* [Page Groups](page-groups.md)
* [Model Descriptor Files](model-json.md)

74
docs/en/low-code/model-json.md

@ -9,26 +9,70 @@
> **Preview:** The Low-Code System is currently in preview. The descriptor format is stable enough for evaluation and source control, but fields may change before general availability.
Low-code metadata is source-controlled as JSON descriptor files used by the Low-Code Designer and React runtime. Current generated projects keep descriptors as split files under `_Dynamic`; older projects may still contain an aggregate `_Dynamic/model.json` document. Use the [Low-Code Designer](designer.md) for normal editing. Use this page when you need to review, generate, merge, or source-control the JSON metadata directly.
Low-code metadata is source-controlled as JSON descriptor files used by the Low-Code Designer and React runtime. Current generated projects keep descriptors as split files under `_Dynamic/model`; older projects may still contain an aggregate `_Dynamic/model.json` document. Use the [Low-Code Designer](designer.md) for normal editing. Use this page when you need to review, generate, merge, or source-control the JSON metadata directly.
## File Location
Generated low-code applications keep descriptor files in a `_Dynamic` folder under the application domain project. A typical project stores one JSON file per descriptor:
Generated low-code applications keep descriptor files under `_Dynamic/model/` plus a generated initializer in the same `_Dynamic` folder. A typical layered application stores one JSON file per descriptor and keeps starter examples separately:
```text
YourApp.Domain/
`-- _Dynamic/
|-- entities/
| `-- Acme.Campaigns.Campaign.json
|-- pages/
| `-- campaigns.json
|-- forms/
| `-- campaign-form.json
`-- permissions/
`-- Acme.Campaigns.json
|-- YourAppLowCodeInitializer.cs
|-- model/
| |-- entities/
| | `-- Acme/Campaigns/Campaign.json
| |-- pages/
| | `-- campaigns.json
| |-- forms/
| | `-- campaign-form.json
| `-- permissions/
| `-- Acme.Campaigns.json
`-- model-examples/
|-- product.entity.json
|-- product-form.form.json
`-- products-page.page.json
```
Exact folders and file names are generated by the tooling for the descriptor type. Keep the whole `_Dynamic` folder and the generated initializer in source control. The low-code module discovers the descriptor metadata during application startup.
Single-layer applications use the same `_Dynamic` layout under the host project instead of the domain project.
Keep the whole `_Dynamic` folder and the generated initializer in source control. The runtime scans `_Dynamic/model/**/*.json`; files in `model-examples/` are ignored until you copy them into the matching category folder under `model/`.
Layout conventions:
* `entities/` and `enums/` usually follow namespace-like folders such as `entities/Acme/Campaigns/Campaign.json`.
* `pageGroups/` usually stays flat as `pageGroups/{groupName}.json`.
* `pages/` stays flat as `pages/{pageName}.json`, even when the page belongs to a page group or is a dashboard.
* Dashboard pages live in `pages/`; there is no separate dashboard descriptor folder.
* The runtime scans the directory tree directly. Do not add a combined index file next to `model/`.
## Example Files and Validation
ABP Studio-generated solutions include a `model-examples/` folder with starter descriptors that are safe to keep in source control but are not loaded by the runtime until copied into `model/`.
Typical example copies:
* `product.entity.json` -> `_Dynamic/model/entities/YourCompany/YourApp/Catalog/Product.json`
* `product-form.form.json` -> `_Dynamic/model/forms/product-form.json`
* `products-page.page.json` -> `_Dynamic/model/pages/products.json`
Copying example files into `model/` activates the metadata, but entity and mapped-property changes still require the generated migration workflow before runtime CRUD pages can query the backing table safely.
After manual JSON edits, validate the descriptor files with the generated startup project before running migrations or shipping changes:
```bash
dotnet run --project <startup-project> -- --check-lowcode-model-files
```
To validate a specific folder instead of the configured source assemblies, pass `--model-directory`:
```bash
dotnet run --project <startup-project> -- --check-lowcode-model-files --model-directory "<path-to-_Dynamic/model>"
```
The file checker reports invalid JSON, wrong category placement, duplicate descriptor names, and model materialization errors before the runtime loads the descriptors.
It does not catch every runtime-semantic UI problem. For example, a form file can still pass the checker while rendering an empty group at runtime if the placement `fieldId` values do not match the form field definitions or if the expected group placements are missing. Use [Health](health.md), the Designer preview, or the runtime page itself to catch those issues.
## JSON Schemas
@ -328,6 +372,8 @@ ZIP file bundle export only includes selected page columns that are file or imag
| `form` | `entityName`, `formName` | Standalone form page |
| `dashboard` | `dashboard` | Dashboard visualizations |
Use [Page Groups](page-groups.md) for the `group` reference and menu nesting rules. Use [Dashboards](dashboards.md) for the nested `dashboard` payload, visualization types, and runtime row grouping model.
Runtime routes use the page name:
```text
@ -341,6 +387,8 @@ Runtime routes use the page name:
Forms are named definitions referenced by pages through `formName`, `createFormName`, or `editFormName`.
Use flat field placements inside each group. The current runtime and designer read `layout.tabs[].groups[].fields[]`, where each item references a `fieldId` and assigns `row`, `colSpan`, and optional `colStart`. If those placements are missing or reference the wrong field IDs, the form can still define fields while a group renders empty.
```json
{
"name": "campaign-form",
@ -584,6 +632,10 @@ In ABP Studio, run the generated migration task for the solution. If you run the
* [Low-Code Designer](designer.md)
* [React Runtime](react-runtime.md)
* [Health](health.md)
* [Dashboards](dashboards.md)
* [Page Groups](page-groups.md)
* [MCP Integration](mcp.md)
* [Attributes & Fluent API](fluent-api.md)
* [Interceptors](interceptors.md)
* [Custom Endpoints](custom-endpoints.md)

123
docs/en/low-code/page-groups.md

@ -0,0 +1,123 @@
```json
//[doc-seo]
{
"Description": "Define ABP Low-Code page groups to organize runtime pages into dynamic menu folders with ordering, icons, and nesting."
}
```
# Page Groups
Page groups are dynamic menu folders used by low-code pages. They let you organize runtime pages into nested navigation structures without hardcoding menu items in the frontend.
## What a Page Group Stores
A page group descriptor stores:
| Field | Description |
|-------|-------------|
| `name` | Stable identifier used by pages in their `group` field |
| `title` | Display label shown in the runtime menu |
| `icon` | CSS class string for the menu icon |
| `order` | Sort order among sibling groups |
| `parent` | Optional parent group name for nesting |
If a group does not define an icon, the runtime menu uses a folder-style default icon.
## How Pages Use Groups
Pages reference a group by name:
```json
{
"name": "orders",
"title": "Orders",
"type": "dataGrid",
"entityName": "Acme.Sales.Order",
"group": "sales"
}
```
If `group` is omitted, the page becomes a top-level runtime menu item.
A group is only a container. It does not define page data, routes, or entity behavior by itself.
## Nesting Rules
Groups can nest by pointing `parent` to another page group name.
Important constraints:
* Group nesting cannot be circular.
* Maximum depth is `3` levels.
* Root groups are groups whose `parent` is empty.
This keeps runtime navigation predictable and avoids unbounded nesting in the generated menu.
## Visibility and Permissions
Page groups do not replace page permissions. Runtime visibility still depends on the pages inside the group.
In practice:
* Pages are added to the runtime menu only if the current user can access them.
* Groups are containers for those visible pages.
* Groups without visible page descendants are omitted from the runtime root menu.
This means page permissions remain the real security boundary, while page groups control menu organization.
## Split Descriptor Example
In split descriptor projects, a page group file in `pageGroups/` stores **one descriptor object**, not a wrapper document:
```json
{
"name": "sales-reports",
"title": "Reports",
"icon": "fa-solid fa-folder-tree",
"order": 20,
"parent": "sales"
}
```
For example, `pageGroups/sales-reports.json` would use that shape directly.
If you are looking at the logical aggregate model instead of split files, the same data appears under the top-level `pageGroups` array:
```json
{
"pageGroups": [
{
"name": "sales",
"title": "Sales",
"icon": "fa-solid fa-chart-line",
"order": 10
},
{
"name": "sales-reports",
"title": "Reports",
"icon": "fa-solid fa-folder-tree",
"order": 20,
"parent": "sales"
}
]
}
```
In split descriptor projects, page groups belong to the `pageGroups/` descriptor category.
## Icon Guidance
Use CSS class strings for icons, for example:
* `fa-solid fa-folder`
* `fa-solid fa-folder-tree`
* `fa-solid fa-chart-line`
Do not treat `icon` as an image URL or file path.
## See Also
* [Dashboards](dashboards.md)
* [React Runtime](react-runtime.md)
* [Low-Code Designer](designer.md)
* [Model Descriptor Files](model-json.md)

9
docs/en/low-code/react-runtime.md

@ -97,6 +97,8 @@ const { data: dynamicMenuItems } = useMenuItems({
Each menu item includes its page name, display name, icon, order, grouping information, and children.
Menu grouping and nesting are defined by [Page Groups](page-groups.md).
## Page Types
The runtime includes built-in renderers for these page types:
@ -110,6 +112,8 @@ The runtime includes built-in renderers for these page types:
| `form` | Standalone form page |
| `dashboard` | Dashboard rows with chart, list, and number visualizations |
See [Dashboards](dashboards.md) for the stored descriptor shape, visualization types, and row grouping rules behind dashboard pages.
The generated data grid page includes:
* Search
@ -129,6 +133,8 @@ The generated data grid page includes:
Create and edit forms are rendered from form metadata. Tabs, groups, labels, placeholders, controls, default values, validation rules, conditional form rules, and save actions come from the designer.
Rendered groups come from the form layout placements under `layout.tabs[].groups[].fields[]`. A form can still load with an empty shell when fields exist but no placements reference them.
![Generated create form](images/runtime-create-form.png)
The runtime can render forms in a modal or on full pages. Full-page forms use the dynamic create/edit routes and the `navigate` callback configured in `configureLowCode`.
@ -185,6 +191,9 @@ Troubleshooting:
| Symptom | Likely cause |
|---------|--------------|
| `403` on a generated page or missing dynamic menu items | The signed-in role or user does not have the generated low-code permissions |
| `Invalid object name ...` after copying source-controlled descriptors | The descriptors were loaded, but the backing table has not been created yet. Run the generated migration or database update task |
| Form shell loads but no fields appear | The form layout is missing `layout.tabs[].groups[].fields[]` placements or the placement `fieldId` values do not match the form fields |
| Invalid or expired download token | The token is single-use, expired, or was requested for a different page/context |
| Export row limit exceeded | Narrow the filters, export the current page, or increase `LowCode:Export:MaxRows` |
| File link expired | Re-run export; temporary file links are intentionally short-lived |

Loading…
Cancel
Save