Browse Source

Merge pull request #25601 from abpframework/salihozkara/low-code-public-schemas

Add low-code model descriptor schemas
pull/25613/head
Engincan VESKE 3 months ago
committed by GitHub
parent
commit
00041faeae
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 37
      schemas/low-code/definitions/command-interceptor-descriptor.schema.json
  2. 40
      schemas/low-code/definitions/dashboard-aggregation-type.schema.json
  3. 91
      schemas/low-code/definitions/dashboard-chart-descriptor.schema.json
  4. 19
      schemas/low-code/definitions/dashboard-chart-type.schema.json
  5. 53
      schemas/low-code/definitions/dashboard-descriptor.schema.json
  6. 47
      schemas/low-code/definitions/dashboard-filter-descriptor.schema.json
  7. 21
      schemas/low-code/definitions/dashboard-global-filter-descriptor.schema.json
  8. 75
      schemas/low-code/definitions/dashboard-list-descriptor.schema.json
  9. 26
      schemas/low-code/definitions/dashboard-number-container-descriptor.schema.json
  10. 77
      schemas/low-code/definitions/dashboard-number-item-descriptor.schema.json
  11. 37
      schemas/low-code/definitions/dashboard-row-descriptor.schema.json
  12. 168
      schemas/low-code/definitions/dashboard-visualization-descriptor.schema.json
  13. 17
      schemas/low-code/definitions/dashboard-visualization-type.schema.json
  14. 64
      schemas/low-code/definitions/endpoint-descriptor.schema.json
  15. 47
      schemas/low-code/definitions/entity-attachment-descriptor.schema.json
  16. 52
      schemas/low-code/definitions/entity-cross-field-validation-descriptor.schema.json
  17. 71
      schemas/low-code/definitions/entity-descriptor.schema.json
  18. 101
      schemas/low-code/definitions/entity-property-descriptor.schema.json
  19. 66
      schemas/low-code/definitions/entity-property-type.schema.json
  20. 56
      schemas/low-code/definitions/enum-descriptor.schema.json
  21. 60
      schemas/low-code/definitions/foreign-key-descriptor.schema.json
  22. 79
      schemas/low-code/definitions/form-descriptor.schema.json
  23. 68
      schemas/low-code/definitions/form-field-descriptor.schema.json
  24. 68
      schemas/low-code/definitions/form-field-type.schema.json
  25. 128
      schemas/low-code/definitions/form-layout-descriptor.schema.json
  26. 82
      schemas/low-code/definitions/form-rule-descriptor.schema.json
  27. 24
      schemas/low-code/definitions/interceptor-type.schema.json
  28. 50
      schemas/low-code/definitions/page-column-descriptor.schema.json
  29. 260
      schemas/low-code/definitions/page-descriptor.schema.json
  30. 119
      schemas/low-code/definitions/page-filter-descriptor.schema.json
  31. 44
      schemas/low-code/definitions/page-group-descriptor.schema.json
  32. 27
      schemas/low-code/definitions/page-permission-config.schema.json
  33. 36
      schemas/low-code/definitions/page-type.schema.json
  34. 44
      schemas/low-code/definitions/permission-descriptor.schema.json
  35. 36
      schemas/low-code/definitions/script-background-job-descriptor.schema.json
  36. 70
      schemas/low-code/definitions/script-background-worker-descriptor.schema.json
  37. 42
      schemas/low-code/definitions/script-event-handler-descriptor.schema.json
  38. 96
      schemas/low-code/definitions/validator-descriptor.schema.json
  39. 19
      schemas/low-code/manifest.json

37
schemas/low-code/definitions/command-interceptor-descriptor.schema.json

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "command-interceptor-descriptor.schema.json",
"title": "CommandInterceptorDescriptor",
"description": "Describes a JavaScript interceptor for an entity Create, Update, or Delete command.",
"markdownDescription": "AI guidance: use `type: \"Pre\"` to validate or block before persistence and `type: \"Post\"` for side effects after persistence. Scripts can inspect `context.commandArgs` and may use services exposed on `context` such as `db`, `currentUser`, `currentTenant`, `emailSender`, `config`, `http`, and logging helpers depending on host configuration. To block the command with a user-facing error, assign `globalError = \"message\"` and return. Keep scripts idempotent where possible.",
"type": "object",
"properties": {
"commandName": {
"type": "string",
"description": "Entity command to intercept: Create, Update, or Delete.",
"enum": ["Create", "Update", "Delete"]
},
"type": {
"$ref": "interceptor-type.schema.json",
"description": "Whether the script runs before or after the command."
},
"javascript": {
"type": "string",
"description": "JavaScript code to execute. For Pre interceptors, set globalError to block the command. Example: if (!context.commandArgs.data['Name']) { globalError = 'Name is required.'; }"
}
},
"required": ["commandName", "type", "javascript"],
"additionalProperties": false,
"examples": [
{
"commandName": "Create",
"type": "Pre",
"javascript": "if (!context.commandArgs.data['Name']) { globalError = 'Name is required.'; }"
},
{
"commandName": "Delete",
"type": "Pre",
"javascript": "var record = await db.get('Acme.Events.Event', context.commandArgs.entityId); if (record && record.Status === 2) { globalError = 'Completed events cannot be deleted.'; }"
}
]
}

40
schemas/low-code/definitions/dashboard-aggregation-type.schema.json

@ -0,0 +1,40 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-aggregation-type.schema.json",
"title": "DashboardAggregationType",
"description": "The type of aggregation to perform. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.",
"markdownDescription": "`count` counts records and does not require a property. `sum`, `average`, `min`, and `max` require a property. `percentFilled` and `percentEmpty` calculate completeness for a nullable/string property.",
"type": "string",
"enum": [
"count",
"Count",
"sum",
"Sum",
"average",
"Average",
"min",
"Min",
"max",
"Max",
"percentFilled",
"PercentFilled",
"percentEmpty",
"PercentEmpty"
],
"enumDescriptions": [
"Count matching records.",
"Count matching records.",
"Sum a numeric property.",
"Sum a numeric property.",
"Average a numeric property.",
"Average a numeric property.",
"Minimum property value.",
"Minimum property value.",
"Maximum property value.",
"Maximum property value.",
"Percentage of records where property has a value.",
"Percentage of records where property has a value.",
"Percentage of records where property is empty/null.",
"Percentage of records where property is empty/null."
]
}

91
schemas/low-code/definitions/dashboard-chart-descriptor.schema.json

@ -0,0 +1,91 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-chart-descriptor.schema.json",
"title": "DashboardChartDescriptor",
"description": "Configuration for a chart visualization.",
"markdownDescription": "AI guidance: use chart visualizations for grouped aggregations. `xAxis.property` is the grouping property. `yAxis` contains one or more aggregations. Use `count` without a property; use `sum`, `average`, `min`, or `max` with a numeric/date property. Use `dateGrouping` when xAxis is date/datetime.",
"type": "object",
"properties": {
"chartType": {
"$ref": "dashboard-chart-type.schema.json",
"description": "Chart renderer type: bar, line, pie, or donut."
},
"xAxis": {
"type": "object",
"properties": {
"property": {
"type": "string",
"description": "Property name to group by on the X-axis. Must exist on the visualization entity."
},
"useForeignDisplay": {
"type": "boolean",
"description": "Show the FK display property instead of the raw id when xAxis.property is a foreign key.",
"default": false
},
"dateGrouping": {
"type": ["string", "null"],
"enum": ["", "day", "week", "month", "quarter", "year", null],
"description": "Grouping interval for date/datetime properties. Omit or use empty string for no date grouping."
}
},
"required": ["property"],
"additionalProperties": false
},
"yAxis": {
"type": "array",
"items": {
"type": "object",
"properties": {
"aggregation": {
"$ref": "dashboard-aggregation-type.schema.json"
},
"property": {
"type": ["string", "null"],
"description": "Property name for sum/average/min/max/percent aggregations. Omit for count."
},
"label": {
"type": ["string", "null"],
"description": "Display label for this series. Omit to derive from aggregation/property."
},
"color": {
"type": ["string", "null"]
}
},
"required": ["aggregation"],
"additionalProperties": false
},
"minItems": 1
},
"barOrientation": {
"type": "string",
"enum": ["vertical", "horizontal"],
"default": "vertical"
},
"showRecordCount": {
"type": "boolean",
"default": false
},
"maxItems": {
"type": "integer",
"description": "Maximum number of grouped items to display in the chart.",
"default": 10,
"minimum": 1,
"maximum": 50
}
},
"required": ["chartType", "xAxis", "yAxis"],
"additionalProperties": false,
"examples": [
{
"chartType": "bar",
"xAxis": { "property": "Status" },
"yAxis": [{ "aggregation": "count", "label": "Events" }],
"maxItems": 10
},
{
"chartType": "line",
"xAxis": { "property": "StartDate", "dateGrouping": "month" },
"yAxis": [{ "aggregation": "sum", "property": "Budget", "label": "Budget" }]
}
]
}

19
schemas/low-code/definitions/dashboard-chart-type.schema.json

@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-chart-type.schema.json",
"title": "DashboardChartType",
"description": "The type of chart. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.",
"markdownDescription": "Use `bar` for categorical comparisons, `line` for trends over time, `pie`/`donut` for part-of-whole views with a small number of categories.",
"type": "string",
"enum": ["bar", "Bar", "line", "Line", "pie", "Pie", "donut", "Donut"],
"enumDescriptions": [
"Bar chart.",
"Bar chart.",
"Line chart.",
"Line chart.",
"Pie chart.",
"Pie chart.",
"Donut chart.",
"Donut chart."
]
}

53
schemas/low-code/definitions/dashboard-descriptor.schema.json

@ -0,0 +1,53 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-descriptor.schema.json",
"title": "DashboardDescriptor",
"description": "Describes a dashboard page configuration with global filters and rows of visualizations.",
"markdownDescription": "AI guidance: a dashboard belongs to a page with `type: \"dashboard\"`. It contains one or more rows; each row contains chart, list, or numberContainer visualizations. Use visualization `entityName` to select data for chart/list items. Use numberContainer for KPI tiles, charts for grouped aggregations, and lists for recent/top records.",
"type": "object",
"properties": {
"description": {
"type": ["string", "null"],
"description": "Optional description text shown below the dashboard title."
},
"globalFilters": {
"type": "array",
"description": "Global filters that affect visualizations, for example a date range. Visualizations can map the global date filter through globalDateFilterProperty.",
"items": {
"$ref": "dashboard-global-filter-descriptor.schema.json"
}
},
"rows": {
"type": "array",
"description": "Dashboard rows. Each row contains one or more visualization items; width 2 items usually take the full row.",
"items": {
"$ref": "dashboard-row-descriptor.schema.json"
},
"minItems": 1
}
},
"required": ["rows"],
"additionalProperties": false,
"examples": [
{
"description": "Operational overview",
"rows": [
{
"items": [
{
"name": "events-by-status",
"type": "chart",
"title": "Events by Status",
"entityName": "Acme.Events.Event",
"chart": {
"chartType": "bar",
"xAxis": { "property": "Status" },
"yAxis": [{ "aggregation": "count", "label": "Events" }]
}
}
]
}
]
}
]
}

47
schemas/low-code/definitions/dashboard-filter-descriptor.schema.json

@ -0,0 +1,47 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-filter-descriptor.schema.json",
"title": "DashboardFilterDescriptor",
"description": "Static filter applied to a dashboard visualization or KPI item.",
"markdownDescription": "AI guidance: use dashboard filters to constrain the data behind a visualization, for example only active records or only records above a threshold. Each condition property must exist on the visualization/item entity. Combine conditions with `operator: \"and\"` or `operator: \"or\"`.",
"type": "object",
"properties": {
"operator": {
"type": "string",
"enum": ["and", "or"],
"default": "and"
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"property": {
"type": "string",
"description": "Property name to filter on. Must exist on the visualization/item entityName."
},
"filterType": {
"type": "string",
"enum": ["equal", "notEqual", "contains", "greaterThan", "lessThan", "isNull", "isNotNull"],
"default": "equal"
},
"value": {
"description": "Filter value. Shape depends on filterType and target property type."
}
},
"required": ["property", "filterType"],
"additionalProperties": false
}
}
},
"required": ["conditions"],
"additionalProperties": false,
"examples": [
{
"operator": "and",
"conditions": [
{ "property": "Status", "filterType": "equal", "value": 1 }
]
}
]
}

21
schemas/low-code/definitions/dashboard-global-filter-descriptor.schema.json

@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-global-filter-descriptor.schema.json",
"title": "DashboardGlobalFilterDescriptor",
"description": "Describes a dashboard-level filter control that can affect all visualizations on the dashboard.",
"markdownDescription": "AI guidance: use global filters for dashboard-wide controls, not for per-visualization conditions. The current supported type is `dateRange`; visualization filters can then reference date-like entity properties through their own `filter` or `userFilters` settings.",
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["dateRange"],
"default": "dateRange",
"description": "Global filter control type. Currently only 'dateRange' is supported."
}
},
"required": ["type"],
"additionalProperties": false,
"examples": [
{ "type": "dateRange" }
]
}

75
schemas/low-code/definitions/dashboard-list-descriptor.schema.json

@ -0,0 +1,75 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-list-descriptor.schema.json",
"title": "DashboardListDescriptor",
"description": "Configuration for a dashboard list/table visualization.",
"markdownDescription": "AI guidance: use list visualizations for recent records, top records, or compact operational queues. `fields` must contain property names from the parent visualization entityName. Use `sortBy` to make results deterministic.",
"type": "object",
"properties": {
"fields": {
"type": "array",
"items": { "type": "string" },
"description": "Property names to display as columns. Each value must reference a property on the visualization entityName."
},
"sortBy": {
"type": "object",
"properties": {
"property": {
"type": "string",
"description": "Property name to sort by. Must exist on the visualization entityName."
},
"direction": {
"type": "string",
"enum": ["asc", "desc"],
"default": "asc"
}
},
"required": ["property"],
"additionalProperties": false
},
"maxRows": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 10
},
"rowHeight": {
"type": "string",
"enum": ["compact", "normal", "tall"],
"default": "compact"
},
"colorBy": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["property", "conditions"]
},
"property": {
"type": "string",
"description": "Enum/status property name for automatic coloring."
}
},
"required": ["type"],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": ["fields"],
"additionalProperties": false,
"examples": [
{
"fields": ["Title", "StartDate", "Status"],
"sortBy": { "property": "StartDate", "direction": "desc" },
"maxRows": 10,
"rowHeight": "compact",
"colorBy": { "type": "property", "property": "Status" }
}
]
}

26
schemas/low-code/definitions/dashboard-number-container-descriptor.schema.json

@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-number-container-descriptor.schema.json",
"title": "DashboardNumberContainerDescriptor",
"description": "A container that holds multiple number/KPI items, each with its own entity and aggregation.",
"markdownDescription": "AI guidance: use numberContainer for KPI tiles. Each item chooses its own entity, aggregation, optional filter, format, and color.",
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "Number/KPI items within this container. Use 1-4 items for a readable dashboard row.",
"items": {
"$ref": "dashboard-number-item-descriptor.schema.json"
}
}
},
"required": ["items"],
"additionalProperties": false,
"examples": [
{
"items": [
{ "name": "total-events", "title": "Total Events", "entityName": "Acme.Events.Event", "aggregation": "count", "format": "number" }
]
}
]
}

77
schemas/low-code/definitions/dashboard-number-item-descriptor.schema.json

@ -0,0 +1,77 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-number-item-descriptor.schema.json",
"title": "DashboardNumberItemDescriptor",
"description": "A single number/KPI tile within a number container.",
"markdownDescription": "AI guidance: `count` does not need aggregationProperty. `sum`, `average`, `min`, `max`, `percentFilled`, and `percentEmpty` should specify aggregationProperty. Use filter to scope the KPI, for example count only active records.",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Unique identifier for this number item. Prefer kebab-case such as 'total-events'.",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title for this KPI."
},
"entityName": {
"type": "string",
"description": "Entity this number item sources data from. Must match an entity descriptor or known reference entity.",
"minLength": 1
},
"aggregation": {
"$ref": "dashboard-aggregation-type.schema.json",
"description": "Aggregation used to compute the KPI value."
},
"aggregationProperty": {
"type": "string",
"description": "Property name for sum/average/min/max/percent aggregations. Omit for count."
},
"format": {
"type": "string",
"enum": ["number", "currency", "percentage"],
"default": "number"
},
"color": {
"type": "string",
"description": "Display color name (blue, green, red, purple, orange, indigo, amber, teal)"
},
"globalDateFilterProperty": {
"type": "string",
"description": "Date/DateTime property used to apply dashboard global date filters. Defaults to CreationTime when omitted."
},
"filter": {
"oneOf": [
{ "$ref": "dashboard-filter-descriptor.schema.json" },
{ "type": "null" }
]
},
"clickToSeeRecords": {
"type": "boolean",
"description": "Allow users to click to see underlying records when supported by the runtime.",
"default": false
}
},
"required": ["name", "title", "entityName", "aggregation"],
"additionalProperties": false,
"examples": [
{
"name": "total-events",
"title": "Total Events",
"entityName": "Acme.Events.Event",
"aggregation": "count",
"format": "number",
"color": "blue"
},
{
"name": "total-budget",
"title": "Total Budget",
"entityName": "Acme.Events.Event",
"aggregation": "sum",
"aggregationProperty": "Budget",
"format": "currency",
"color": "green"
}
]
}

37
schemas/low-code/definitions/dashboard-row-descriptor.schema.json

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-row-descriptor.schema.json",
"title": "DashboardRowDescriptor",
"description": "Describes one row in the dashboard layout grid. Each row contains one or two visualization items.",
"markdownDescription": "AI guidance: organize dashboard visualizations into rows by visual importance. Use one item for a full-width chart/list/number panel, and two items when related visualizations should be displayed side by side. Do not put more than two visualizations in a row; create another row instead.",
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "Visualization items in this row. Use one item for full-width content or two items for a two-column row.",
"items": {
"$ref": "dashboard-visualization-descriptor.schema.json"
},
"minItems": 1,
"maxItems": 2
}
},
"required": ["items"],
"additionalProperties": false,
"examples": [
{
"items": [
{
"name": "overview",
"type": "numberContainer",
"title": "Overview",
"numberContainer": {
"items": [
{ "name": "total-records", "title": "Total Records", "entityName": "Acme.Events.Event", "aggregation": "count" }
]
}
}
]
}
]
}

168
schemas/low-code/definitions/dashboard-visualization-descriptor.schema.json

@ -0,0 +1,168 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-visualization-descriptor.schema.json",
"title": "DashboardVisualizationDescriptor",
"description": "A single visualization element in a dashboard row: chart, list, or number container.",
"markdownDescription": "AI guidance: set `type` and then provide the matching payload: `chart` for type chart, `list` for type list, or `numberContainer` for type numberContainer. Do not populate unrelated payloads. Chart and list visualizations require `entityName`; number containers define entityName per KPI item.",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Unique identifier within the dashboard. Prefer kebab-case such as 'events-by-status'.",
"minLength": 1
},
"type": {
"$ref": "dashboard-visualization-type.schema.json",
"description": "Visualization renderer type. Determines which payload property must be populated."
},
"title": {
"type": "string",
"description": "Display title for the visualization."
},
"description": {
"type": ["string", "null"],
"description": "Optional description text. Can be shown inline or as tooltip depending on showDescriptionAsTooltip."
},
"width": {
"type": "integer",
"enum": [1, 2],
"default": 1,
"description": "Column width: 1 = half row, 2 = full row."
},
"entityName": {
"type": "string",
"description": "Entity this visualization sources data from. Required for chart and list visualizations; numberContainer items define their own entityName."
},
"globalDateFilterProperty": {
"type": "string",
"description": "Date/DateTime property used to apply dashboard global date filters to this visualization. Defaults to CreationTime when omitted."
},
"filter": {
"oneOf": [
{ "$ref": "dashboard-filter-descriptor.schema.json" },
{ "type": "null" }
]
},
"userFilters": {
"type": "array",
"description": "Interactive filters exposed to end users on this visualization.",
"items": {
"type": "object",
"properties": {
"property": {
"type": "string",
"description": "Property name to filter on. Must exist on entityName.",
"minLength": 1
}
},
"required": ["property"],
"additionalProperties": false
}
},
"showDescriptionAsTooltip": {
"type": "boolean",
"default": false
},
"clickToSeeRecords": {
"type": "boolean",
"description": "Allow users to click to see underlying records when supported by the runtime.",
"default": false
},
"chart": {
"oneOf": [
{ "$ref": "dashboard-chart-descriptor.schema.json" },
{ "type": "null" }
]
},
"list": {
"oneOf": [
{ "$ref": "dashboard-list-descriptor.schema.json" },
{ "type": "null" }
]
},
"numberContainer": {
"oneOf": [
{ "$ref": "dashboard-number-container-descriptor.schema.json" },
{ "type": "null" }
]
}
},
"required": ["name", "type", "title"],
"allOf": [
{
"if": {
"required": ["type"],
"properties": {
"type": { "enum": ["chart", "Chart"] }
}
},
"then": {
"required": ["chart", "entityName"],
"properties": {
"chart": { "$ref": "dashboard-chart-descriptor.schema.json" },
"entityName": {
"type": "string",
"minLength": 1
}
}
}
},
{
"if": {
"required": ["type"],
"properties": {
"type": { "enum": ["list", "List"] }
}
},
"then": {
"required": ["list", "entityName"],
"properties": {
"list": { "$ref": "dashboard-list-descriptor.schema.json" },
"entityName": {
"type": "string",
"minLength": 1
}
}
}
},
{
"if": {
"required": ["type"],
"properties": {
"type": { "enum": ["numberContainer", "NumberContainer"] }
}
},
"then": {
"required": ["numberContainer"],
"properties": {
"numberContainer": { "$ref": "dashboard-number-container-descriptor.schema.json" }
}
}
}
],
"additionalProperties": false,
"examples": [
{
"name": "events-by-status",
"type": "chart",
"title": "Events by Status",
"entityName": "Acme.Events.Event",
"chart": {
"chartType": "bar",
"xAxis": { "property": "Status" },
"yAxis": [{ "aggregation": "count", "label": "Events" }]
}
},
{
"name": "recent-events",
"type": "list",
"title": "Recent Events",
"entityName": "Acme.Events.Event",
"list": {
"fields": ["Title", "StartDate", "Status"],
"sortBy": { "property": "StartDate", "direction": "desc" },
"maxRows": 10
}
}
]
}

17
schemas/low-code/definitions/dashboard-visualization-type.schema.json

@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dashboard-visualization-type.schema.json",
"title": "DashboardVisualizationType",
"description": "The type of dashboard visualization. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.",
"markdownDescription": "`chart` groups/aggregates entity data into a bar/line/pie/donut chart. `list` displays recent/top records from one entity. `numberContainer` displays one or more KPI number tiles, each with its own entity and aggregation.",
"type": "string",
"enum": ["chart", "Chart", "list", "List", "numberContainer", "NumberContainer"],
"enumDescriptions": [
"Chart visualization with x/y axis aggregation.",
"Chart visualization with x/y axis aggregation.",
"Record list visualization.",
"Record list visualization.",
"KPI number/tile container.",
"KPI number/tile container."
]
}

64
schemas/low-code/definitions/endpoint-descriptor.schema.json

@ -0,0 +1,64 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "endpoint-descriptor.schema.json",
"title": "Custom Endpoint Descriptor",
"description": "Defines a custom HTTP endpoint that executes server-side JavaScript code.",
"markdownDescription": "AI guidance: use custom endpoints for model-owned actions and lightweight APIs. `name` must be unique. `route` should start with `/api/` and must not conflict with another route/method. Use `{id}` style path parameters when needed. Scripts can access request data through the endpoint context and return HTTP results with helpers such as `context.ok(value)`, `context.created(value)`, and `context.noContent()` where available. Require authentication by default and add `requiredPermissions` for protected operations.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique endpoint identifier used by designer/model health. Prefer PascalCase or kebab-case, for example 'SearchCustomers'.",
"minLength": 1
},
"route": {
"type": "string",
"description": "URL route pattern. Must start with '/' and should use an application-specific prefix such as '/api/low-code/events/{id}'. Route parameters use ASP.NET style braces, for example '{id}'.",
"minLength": 1,
"pattern": "^/"
},
"method": {
"type": "string",
"description": "HTTP method. GET should be read-only; POST/PUT/PATCH/DELETE may mutate state.",
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH"],
"default": "GET"
},
"javascript": {
"type": "string",
"description": "JavaScript code to execute. Use context request/response helpers and services exposed by the host, such as db, currentUser/currentTenant, authorization, emailSender, config, http, event bus, background jobs, and logging helpers.",
"minLength": 1
},
"requireAuthentication": {
"type": "boolean",
"description": "Whether authentication is required. Keep true unless this endpoint is intentionally public.",
"default": true
},
"requiredPermissions": {
"type": "array",
"description": "Permission names required to access the endpoint. Checked only when authentication is required. Values should reference custom permissions or known static permissions.",
"items": {
"type": "string"
}
},
"description": {
"type": "string",
"description": "Optional human-readable description for designer documentation and model health context."
}
},
"required": ["name", "route", "javascript"],
"additionalProperties": false,
"examples": [
{
"name": "SearchCustomers",
"route": "/api/low-code/customers/search",
"method": "GET",
"requireAuthentication": true,
"requiredPermissions": ["Acme.Customers.View"],
"javascript": "var q = context.request.query.q || ''; var table = await db.query('Acme.Crm.Customer'); var rows = await table.where(c => c.Name.toLowerCase().includes(q.toLowerCase())).take(10).toList(); return context.ok(rows);"
}
]
}

47
schemas/low-code/definitions/entity-attachment-descriptor.schema.json

@ -0,0 +1,47 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entity-attachment-descriptor.schema.json",
"title": "EntityAttachmentDescriptor",
"description": "Configures record-level attachments for an entity.",
"markdownDescription": "AI guidance: use entity attachments when records need a collection of arbitrary files. Use File/Image entity properties instead when the file is a named business field such as `ContractPdf` or `CoverImage`.",
"type": "object",
"properties": {
"isEnabled": {
"type": "boolean",
"description": "When true, records of this entity can have attachments."
},
"maxFileCount": {
"type": "integer",
"description": "Maximum number of files allowed per entity record.",
"minimum": 1
},
"maxFileSizeBytes": {
"type": "integer",
"description": "Maximum allowed size in bytes for a single attachment.",
"minimum": 1
},
"maxTotalSizeBytes": {
"type": "integer",
"description": "Maximum total attachment size in bytes per entity record.",
"minimum": 1
},
"allowedContentTypes": {
"type": "array",
"description": "Allowed MIME content types, wildcard MIME patterns, or file extensions. Examples: 'image/*', 'application/pdf', '.docx'.",
"items": {
"type": "string",
"minLength": 1
}
}
},
"required": ["isEnabled"],
"additionalProperties": false,
"examples": [
{
"isEnabled": true,
"maxFileCount": 5,
"maxFileSizeBytes": 5242880,
"allowedContentTypes": ["image/*", "application/pdf"]
}
]
}

52
schemas/low-code/definitions/entity-cross-field-validation-descriptor.schema.json

@ -0,0 +1,52 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entity-cross-field-validation-descriptor.schema.json",
"title": "EntityCrossFieldValidationDescriptor",
"description": "Describes an entity-level validation rule that compares one property against another property on the same entity.",
"markdownDescription": "AI guidance: use cross-field validations for rules such as EndDate > StartDate, Min <= Max, PasswordRepeat == Password, or PublishedOn >= CreatedOn. `propertyName` receives the validation error; `otherPropertyName` is the comparison target. Both must exist on the entity.",
"type": "object",
"properties": {
"propertyName": {
"type": "string",
"description": "Property that receives the validation error when the rule fails. Must exist on the entity.",
"minLength": 1
},
"operator": {
"type": "string",
"description": "Comparison operator",
"enum": [
"equals",
"notEquals",
"greaterThan",
"greaterThanOrEqual",
"lessThan",
"lessThanOrEqual"
]
},
"otherPropertyName": {
"type": "string",
"description": "Property to compare against. Must exist on the same entity.",
"minLength": 1
},
"message": {
"type": "string",
"description": "Optional custom validation message"
}
},
"required": ["propertyName", "operator", "otherPropertyName"],
"additionalProperties": false,
"examples": [
{
"propertyName": "EndDate",
"operator": "greaterThan",
"otherPropertyName": "StartDate",
"message": "End Date must be greater than Start Date."
},
{
"propertyName": "PasswordRepeat",
"operator": "equals",
"otherPropertyName": "Password",
"message": "Password repeat must match Password."
}
]
}

71
schemas/low-code/definitions/entity-descriptor.schema.json

@ -0,0 +1,71 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entity-descriptor.schema.json",
"title": "EntityDescriptor",
"description": "Describes a dynamic entity. An entity is the runtime data model: it defines a table-like aggregate, its properties, validations, parent-child relation, attachment support, and create/update/delete interceptors.",
"markdownDescription": "AI guidance: create one entity per aggregate/root concept. Use a stable namespace-style `name` such as `Acme.Crm.Customer`; this name is referenced by pages, forms, foreign keys, dashboards, scripts, and permissions. Put user-facing labels in `displayName`; choose `displayProperty` as a short string property used by lookups. Use `parent` only for child/detail entities that belong to a parent record.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Stable full name of the entity, usually Namespace.Module.EntityName (for example 'Acme.Crm.Customer'). Must be unique across all model layers. Do not rename after data exists unless a migration/rename flow is intended.",
"minLength": 1
},
"displayName": {
"type": "string",
"description": "Default plural/screen label for this entity (for example 'Customers'). Page menu titles are configured separately on page descriptors.",
"minLength": 1
},
"displayProperty": {
"type": "string",
"description": "Property name used when another entity references this entity in a lookup/autocomplete. Prefer a required string property such as 'Name', 'Title', or 'Code'."
},
"parent": {
"type": "string",
"description": "Full name of the parent entity for parent-child/detail entities. When set, records of this entity are scoped under the parent and should include an FK property to the parent.",
"minLength": 1
},
"attachments": {
"$ref": "entity-attachment-descriptor.schema.json",
"description": "Record-level attachment settings. Use this for multiple arbitrary files attached to a record; use File/Image properties for first-class file fields."
},
"properties": {
"type": "array",
"description": "Entity properties. Omit framework audit/id fields such as Id, CreationTime, CreatorId, LastModificationTime, IsDeleted unless intentionally overriding metadata; the runtime supplies standard fields.",
"items": {
"$ref": "entity-property-descriptor.schema.json"
}
},
"crossFieldValidations": {
"type": "array",
"description": "Entity-level validation rules that compare two properties from the same entity. Use for date ranges, matching password fields, numeric min/max pairs, and other cross-field rules.",
"items": {
"$ref": "entity-cross-field-validation-descriptor.schema.json"
}
},
"interceptors": {
"type": "array",
"description": "Create/Update/Delete command interceptors. Use Pre interceptors to validate, normalize, or block a command; use Post interceptors for side effects after persistence.",
"items": {
"$ref": "command-interceptor-descriptor.schema.json"
}
}
},
"required": ["name"],
"additionalProperties": false,
"examples": [
{
"name": "Acme.Crm.Customer",
"displayName": "Customers",
"displayProperty": "Name",
"properties": [
{ "name": "Name", "type": "string", "isRequired": true },
{ "name": "Email", "type": "string", "validators": [{ "type": "email" }] }
]
}
]
}

101
schemas/low-code/definitions/entity-property-descriptor.schema.json

@ -0,0 +1,101 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entity-property-descriptor.schema.json",
"title": "EntityPropertyDescriptor",
"description": "Describes one dynamic entity property. Properties define persisted data fields, enum fields, foreign keys, upload fields, server-only fields, defaults, uniqueness, and validators.",
"markdownDescription": "AI guidance: use PascalCase property names. Use `type` for primitive fields, `type: \"enum\"` with `enumType` for enum fields, and a `foreignKey` object for lookup fields. FK property names should normally end with `Id`. Keep sensitive values `serverOnly: true`. Do not add legacy UI configuration here; page columns/filters and form fields own UI behavior.",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Stable PascalCase property name, unique within the entity. Examples: 'Name', 'EmailAddress', 'CustomerId', 'StartDate'.",
"minLength": 1
},
"type": {
"$ref": "entity-property-type.schema.json",
"description": "Primitive or special property type. If omitted, runtime treats the property as string. Use 'enum' only with enumType; use 'file'/'image' for first-class upload fields."
},
"displayName": {
"type": "string",
"description": "Default display label for this property. Page columns and form fields can override it. Omit when the label can be derived from the property name.",
"minLength": 1
},
"enumType": {
"type": "string",
"description": "Name of a JSON-defined enum from top-level enums or a full type name for code enums. Required when type is 'enum'."
},
"allowSetByClients": {
"type": "boolean",
"description": "Controls whether create/update clients may set the property. Use false for server-computed or protected fields that can still be returned to clients."
},
"serverOnly": {
"type": "boolean",
"description": "When true, this property is completely hidden from clients, API responses, and UI definitions. Use for secrets, internal notes, hashes, or backend-only workflow state."
},
"isMappedToDbField": {
"type": "boolean",
"description": "Whether this property is mapped to a database column. Keep true or omit for normal persisted fields. Use false for computed/transient fields supplied by scripts or backend logic."
},
"defaultValue": {
"type": ["string", "null"],
"description": "Default value for new records. Stored as a string in descriptor JSON and converted to the declared property type at runtime. Examples: '0' for int/enum, 'true' for boolean, '2026-05-18T09:00:00Z' for datetime."
},
"isUnique": {
"type": "boolean",
"description": "Whether this property value must be unique across records of this entity. Use for codes, slugs, natural keys, and names only when duplicates are not allowed."
},
"isRequired": {
"type": "boolean",
"description": "When true, the property is required/not nullable. This affects database schema, backend validation, and generated UI validation. Existing data may need defaults before turning this on."
},
"foreignKey": {
"$ref": "foreign-key-descriptor.schema.json",
"description": "Foreign key/lookup relation. The current property's value stores the referenced entity id. The property name should usually be '<ReferencedEntity>NameId', for example 'CustomerId'."
},
"fileMaxSizeBytes": {
"type": "integer",
"description": "Maximum allowed file size in bytes for File and Image properties. Example: 5242880 for 5 MiB.",
"minimum": 1
},
"fileAllowedContentTypes": {
"type": "array",
"description": "Allowed MIME content types or wildcard patterns for File and Image properties. Examples: 'image/*', 'application/pdf'.",
"items": {
"type": "string",
"minLength": 1
}
},
"imageMaxWidth": {
"type": "integer",
"description": "Optional maximum image width in pixels.",
"minimum": 1
},
"imageMaxHeight": {
"type": "integer",
"description": "Optional maximum image height in pixels.",
"minimum": 1
},
"imageResizeMode": {
"type": "string",
"description": "How uploaded images should be resized when image dimensions are configured. 'fit' preserves the full image inside the bounds; 'fill' crops/fills the target bounds.",
"enum": ["fit", "fill"]
},
"validators": {
"type": "array",
"description": "Backend/UI validators for this property. Use required, length, range, pattern, email, phone, url, or creditCard as appropriate. Duplicate required can be omitted when isRequired is true unless a custom message is needed.",
"items": {
"$ref": "validator-descriptor.schema.json"
}
}
},
"required": [
"name"
],
"additionalProperties": false,
"examples": [
{ "name": "Name", "type": "string", "isRequired": true, "validators": [{ "type": "maxLength", "length": 128 }] },
{ "name": "Status", "type": "enum", "enumType": "Acme.Events.EventStatus", "defaultValue": "0" },
{ "name": "CustomerId", "foreignKey": { "entityName": "Acme.Crm.Customer", "displayPropertyName": "Name" } },
{ "name": "CoverImage", "type": "image", "fileAllowedContentTypes": ["image/*"], "fileMaxSizeBytes": 5242880, "imageResizeMode": "fit" }
]
}

66
schemas/low-code/definitions/entity-property-type.schema.json

@ -0,0 +1,66 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entity-property-type.schema.json",
"title": "EntityPropertyType",
"description": "Data type of an entity property. Use canonical lowercase values for new descriptors and MCP mutations; compatibility aliases are accepted for older descriptors.",
"markdownDescription": "Use canonical lowercase values in MCP/write payloads: `string`, `int`, `long`, `decimal`, `datetime`, `boolean`, `guid`, `enum`, `date`, `time`, `file`, `image`, or `money`. `string` stores text; `int`/`long` store whole numbers; `decimal` and `money` store numeric decimals; `datetime`, `date`, and `time` model temporal values; `boolean` stores true/false; `guid` stores GUID values; `enum` requires `enumType`; `file` and `image` store upload metadata/content through the low-code file pipeline. Legacy aliases such as `String`, `Int`, `dateTime`, and `DateTime` remain accepted by the schema for compatibility, but new payloads should not use .NET aliases like `Int32`.",
"type": "string",
"enum": [
"string",
"String",
"int",
"Int",
"long",
"Long",
"decimal",
"Decimal",
"datetime",
"dateTime",
"DateTime",
"boolean",
"Boolean",
"guid",
"Guid",
"enum",
"Enum",
"date",
"Date",
"time",
"Time",
"file",
"File",
"image",
"Image",
"money",
"Money"
],
"enumDescriptions": [
"Text/string value.",
"Text/string value.",
"32-bit whole number.",
"32-bit whole number.",
"64-bit whole number.",
"64-bit whole number.",
"Decimal numeric value.",
"Decimal numeric value.",
"Date and time value.",
"Date and time value.",
"Date and time value.",
"Boolean true/false value.",
"Boolean true/false value.",
"GUID/UUID value.",
"GUID/UUID value.",
"Integer-backed enum value; set enumType.",
"Integer-backed enum value; set enumType.",
"Date-only value.",
"Date-only value.",
"Time-only value.",
"Time-only value.",
"Uploaded file field.",
"Uploaded file field.",
"Uploaded image field with optional dimension constraints.",
"Uploaded image field with optional dimension constraints.",
"Money amount rendered with money-aware controls.",
"Money amount rendered with money-aware controls."
]
}

56
schemas/low-code/definitions/enum-descriptor.schema.json

@ -0,0 +1,56 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "enum-descriptor.schema.json",
"title": "EnumDescriptor",
"description": "Describes an integer-backed enum definition for use in entity properties and select/kanban UI.",
"markdownDescription": "AI guidance: define enums before entity properties that reference them. Use a stable namespace-style `name` such as `Acme.Events.EventStatus`. Each value name should be PascalCase and unique within the enum. Use explicit integer `value`s for stable persistence; do not reorder or renumber after data exists.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Stable unique enum name. Use a namespace-style name if the enum belongs to a module/domain, for example 'Acme.Events.EventStatus'.",
"minLength": 1
},
"values": {
"type": "array",
"description": "Ordered list of integer-backed enum values. Values are stored as integers; names are used for display and model readability.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "PascalCase enum value name, for example 'Draft', 'Scheduled', or 'Completed'."
},
"value": {
"type": "integer",
"description": "Stable integer value. Prefer explicit values starting at 0 so generated descriptor JSON is deterministic."
}
},
"required": [
"name"
],
"additionalProperties": false
},
"minItems": 1
}
},
"required": [
"name",
"values"
],
"additionalProperties": false,
"examples": [
{
"name": "Acme.Events.EventStatus",
"values": [
{ "name": "Draft", "value": 0 },
{ "name": "Scheduled", "value": 1 },
{ "name": "Completed", "value": 2 }
]
}
]
}

60
schemas/low-code/definitions/foreign-key-descriptor.schema.json

@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "foreign-key-descriptor.schema.json",
"title": "ForeignKeyDescriptor",
"description": "Describes a foreign key/lookup relationship from the owning entity property to another entity or reference entity.",
"markdownDescription": "AI guidance: put `foreignKey` on the property that stores the related record id. The property name should usually end with `Id`, for example `CustomerId`. `entityName` must match a model entity name or a registered code/reference entity such as `Volo.Abp.Identity.IdentityUser`. Use `displayPropertyName` when the target display field is not obvious. Use `dependsOn` for cascading dropdowns such as City filtered by Country. Use `access` only for reverse access from the referenced entity side: `none`, `view`, or `edit`. Do not use `lookup` as `access`; lookup is a form field/control type.",
"type": "object",
"properties": {
"entityName": {
"type": "string",
"description": "Full name of the related entity or registered reference entity. Must match an entity descriptor name or a known code/reference entity.",
"minLength": 1
},
"displayPropertyName": {
"type": "string",
"description": "Property name to display from the related entity in lookups/autocomplete. Omit to use the target entity displayProperty.",
"minLength": 1
},
"access": {
"type": "string",
"description": "Access level for managing this relation from the referenced entity side. 'none' means no reverse access; 'view' allows the referenced entity page to show related records; 'edit' allows managing related records from the referenced side.",
"enum": ["none", "None", "view", "View", "edit", "Edit"],
"default": "none"
},
"dependsOn": {
"type": "object",
"description": "Cascading dependency: filter this FK lookup by the value of another FK property on the same owning entity. Example: CityId depends on CountryId and filters City.CountryId.",
"properties": {
"propertyName": {
"type": "string",
"description": "Property name on the owning entity whose value provides the filter (for example 'CountryId' on an Address entity).",
"minLength": 1
},
"filterPropertyName": {
"type": "string",
"description": "Property name on the target lookup entity to filter by (for example 'CountryId' on City).",
"minLength": 1
}
},
"required": ["propertyName", "filterPropertyName"],
"additionalProperties": false
}
},
"required": ["entityName"],
"additionalProperties": false,
"examples": [
{
"entityName": "Acme.Crm.Customer",
"displayPropertyName": "Name"
},
{
"entityName": "Acme.Geo.City",
"displayPropertyName": "Name",
"dependsOn": {
"propertyName": "CountryId",
"filterPropertyName": "CountryId"
}
}
]
}

79
schemas/low-code/definitions/form-descriptor.schema.json

@ -0,0 +1,79 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "form-descriptor.schema.json",
"title": "FormDescriptor",
"description": "Describes a named create/edit form definition bound to one entity.",
"markdownDescription": "AI guidance: a form is referenced by page `formName`, `createFormName`, or `editFormName`. Keep `entityName` aligned with the page entityName. Define all fields in `fields`, then place every visible field in `layout.tabs[].groups[].rows[].cells[]` by field id. Use form `rules` for conditional visibility/enabled state; use entity validators for core data validation.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Stable unique form identifier. Prefer kebab-case, for example 'customer-form' or 'event-form'. Pages reference this value.",
"minLength": 1
},
"entityName": {
"type": "string",
"description": "Full name of the entity this form is bound to. Must match the page entityName that uses this form.",
"minLength": 1
},
"enableSaveAndNew": {
"type": "boolean",
"description": "Whether the form should expose a Save and New action in addition to the standard save action. Useful for rapid data entry.",
"default": false
},
"fields": {
"type": "array",
"description": "Flat list of all fields in this form. Field ids must be unique within the form; bound fields should point to properties on entityName.",
"items": {
"$ref": "form-field-descriptor.schema.json"
}
},
"layout": {
"$ref": "form-layout-descriptor.schema.json",
"description": "Visual layout for the fields. Every layout cell fieldId must refer to a field in fields."
},
"rules": {
"type": "array",
"description": "Conditional rules for field/group visibility, enabled state, and value setting. Use for simple client-side form behavior.",
"items": {
"$ref": "form-rule-descriptor.schema.json"
}
}
},
"required": ["name", "entityName", "fields", "layout"],
"additionalProperties": false,
"examples": [
{
"name": "event-form",
"entityName": "Acme.Events.Event",
"fields": [
{ "id": "title", "label": "Title", "type": "text", "binding": "Title" },
{ "id": "status", "label": "Status", "type": "select", "binding": "Status", "enumType": "Acme.Events.EventStatus" }
],
"layout": {
"tabs": [
{
"id": "main",
"title": "Main",
"isDefault": true,
"groups": [
{
"id": "details",
"title": "Details",
"isDefault": true,
"rows": [
{ "cells": [{ "fieldId": "title", "colSpan": 4 }] },
{ "cells": [{ "fieldId": "status", "colSpan": 2 }] }
]
}
]
}
]
}
}
]
}

68
schemas/low-code/definitions/form-field-descriptor.schema.json

@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "form-field-descriptor.schema.json",
"title": "FormFieldDescriptor",
"description": "Describes a single field in a form. A field may be bound to an entity property or unbound for computed/display-only UI.",
"markdownDescription": "AI guidance: use a stable camelCase `id` for each field. For ordinary data entry, set `binding` to an entity property and choose a field `type` compatible with that property. For enum selects, set `type: \"select\"` and `enumType`. For FK lookups, use `type: \"lookup\"` and bind to the FK property. Put fields into layout cells by id.",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this field within the form. Prefer camelCase, for example 'title' or 'customerId'. Layout cells and rules reference this id.",
"minLength": 1
},
"label": {
"type": "string",
"description": "Display label for the field.",
"minLength": 1
},
"type": {
"$ref": "form-field-type.schema.json",
"description": "Visual/input control type. Choose a type compatible with the bound entity property."
},
"binding": {
"type": ["string", "null"],
"description": "Entity property name to bind to, or null/omitted for unbound fields. Supports dotted paths like 'Parent.Name' for related entity display where supported."
},
"enumType": {
"type": "string",
"description": "Enum name for select fields bound to enum properties. Must match the entity property's enumType or a known code enum."
},
"defaultValue": {
"description": "Default field value used by the UI when creating a new record. Prefer entity property defaultValue for persisted defaults."
},
"placeholder": {
"type": "string",
"description": "Placeholder text for the input"
},
"helpText": {
"type": "string",
"description": "Help text displayed below the field"
},
"readOnly": {
"type": "boolean",
"description": "Whether the field is read-only in the form UI. This does not by itself protect backend writes; use allowSetByClients/serverOnly for security.",
"default": false
},
"modeVisibility": {
"type": "string",
"enum": ["both", "Both", "createOnly", "CreateOnly", "editOnly", "EditOnly"],
"description": "Controls in which form mode the field is visible: both, createOnly, or editOnly.",
"default": "both"
},
"validations": {
"type": "array",
"description": "Form-level validation rules that supplement entity-level validators. Use when validation is specific to this form.",
"items": {
"$ref": "validator-descriptor.schema.json"
}
}
},
"required": ["id", "label", "type"],
"additionalProperties": false,
"examples": [
{ "id": "title", "label": "Title", "type": "text", "binding": "Title", "validations": [{ "type": "required" }] },
{ "id": "status", "label": "Status", "type": "select", "binding": "Status", "enumType": "Acme.Events.EventStatus" },
{ "id": "customerId", "label": "Customer", "type": "lookup", "binding": "CustomerId" }
]
}

68
schemas/low-code/definitions/form-field-type.schema.json

@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "form-field-type.schema.json",
"title": "FormFieldType",
"description": "Available form field control types. Use canonical lowercase values for new descriptors and MCP mutations; compatibility aliases are accepted for older descriptors.",
"markdownDescription": "Use canonical lowercase values in MCP/write payloads: `text`, `textarea`, `number`, `checkbox`, `date`, `datetime`, `select`, `lookup`, `guid`, `computed`, `time`, `file`, `image`, or `money`. `text`/`textarea` are for strings, `number` for numeric values, `checkbox` for boolean, `date`/`datetime`/`time` for temporal values, `select` for enum values, `lookup` for foreign keys, `guid` for GUID input/display, `computed` for unbound calculated/display fields, `file` and `image` for upload fields, and `money` for money/decimal amount input. There is no `email` field type; use `text` plus validations where needed. Legacy PascalCase aliases remain accepted by the schema for compatibility.",
"type": "string",
"enum": [
"text",
"Text",
"textarea",
"Textarea",
"number",
"Number",
"checkbox",
"Checkbox",
"date",
"Date",
"datetime",
"DateTime",
"select",
"Select",
"lookup",
"Lookup",
"guid",
"Guid",
"computed",
"Computed",
"time",
"Time",
"file",
"File",
"image",
"Image",
"money",
"Money"
],
"enumDescriptions": [
"Single-line text input.",
"Single-line text input.",
"Multi-line text input.",
"Multi-line text input.",
"Numeric input.",
"Numeric input.",
"Boolean checkbox.",
"Boolean checkbox.",
"Date-only input.",
"Date-only input.",
"Date and time input.",
"Date and time input.",
"Enum/select input.",
"Enum/select input.",
"Foreign key lookup/autocomplete input.",
"Foreign key lookup/autocomplete input.",
"GUID input/display.",
"GUID input/display.",
"Unbound computed/display field.",
"Unbound computed/display field.",
"Time-only input.",
"Time-only input.",
"File upload field.",
"File upload field.",
"Image upload field.",
"Image upload field.",
"Money amount input.",
"Money amount input."
]
}

128
schemas/low-code/definitions/form-layout-descriptor.schema.json

@ -0,0 +1,128 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "form-layout-descriptor.schema.json",
"title": "FormLayoutDescriptor",
"description": "Describes the visual layout of a form as tabs, groups, rows, and field cells.",
"markdownDescription": "AI guidance: the layout is a tree: tabs -> groups -> rows -> cells. Each cell `fieldId` must reference a field from the form's `fields` array. Use `colSpan` 4 for full-width fields, 2+2 for two columns, or 1+1+1+1 for four compact controls. The total effective width in a row should not exceed 4.",
"type": "object",
"properties": {
"tabs": {
"type": "array",
"description": "Ordered list of tabs in the form. Use one default tab named 'main' for simple forms.",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this tab. Prefer camelCase/kebab-case such as 'main' or 'advanced'.",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title for the tab",
"minLength": 1
},
"isDefault": {
"type": "boolean",
"description": "Whether this is the default tab. The designer uses the default tab as the safe target for orphaned fields.",
"default": false
},
"groups": {
"type": "array",
"description": "Ordered list of groups within this tab. Use one default group for simple forms.",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this group. Rules can target group ids.",
"minLength": 1
},
"title": {
"type": ["string", "null"],
"description": "Optional display title for the group. Use null or omit for an untitled group."
},
"isDefault": {
"type": "boolean",
"description": "Whether this is the default group. The designer uses the default group as the safe target for orphaned fields.",
"default": false
},
"rows": {
"type": "array",
"description": "Ordered list of layout rows; each row contains one or more cells placed side-by-side.",
"items": {
"type": "object",
"properties": {
"cells": {
"type": "array",
"description": "Fields placed side-by-side in this row. The sum of colSpan values should not exceed 4.",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"fieldId": {
"type": "string",
"description": "Reference to a field id in the form's fields array. Every field shown in the layout needs a matching field descriptor.",
"minLength": 1
},
"colSpan": {
"type": "integer",
"description": "Number of grid columns this field spans from 1 to 4. Use 4 for full-width fields.",
"minimum": 1,
"maximum": 4,
"default": 4
},
"colStart": {
"type": ["integer", "null"],
"description": "Starting grid column from 1 to 4. Omit or null to auto-place after the previous cell.",
"minimum": 1,
"maximum": 4
}
},
"required": ["fieldId"],
"additionalProperties": false
}
}
},
"required": ["cells"],
"additionalProperties": false
}
}
},
"required": ["id", "rows"],
"additionalProperties": false
}
}
},
"required": ["id", "title", "groups"],
"additionalProperties": false
}
}
},
"required": ["tabs"],
"additionalProperties": false,
"examples": [
{
"tabs": [
{
"id": "main",
"title": "Main",
"isDefault": true,
"groups": [
{
"id": "details",
"title": "Details",
"isDefault": true,
"rows": [
{ "cells": [{ "fieldId": "title", "colSpan": 4 }] },
{ "cells": [{ "fieldId": "startDate", "colSpan": 2 }, { "fieldId": "endDate", "colSpan": 2 }] }
]
}
]
}
]
}
]
}

82
schemas/low-code/definitions/form-rule-descriptor.schema.json

@ -0,0 +1,82 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "form-rule-descriptor.schema.json",
"title": "FormRuleDescriptor",
"description": "Describes a conditional form rule with one or more actions that execute when the condition is met.",
"markdownDescription": "AI guidance: use rules for simple client-side behavior such as hiding a group when a checkbox is false, disabling a field after a status is selected, or setting a default value. `condition.fieldId` and every action `targetId` must reference existing field/group ids. Rules are not security boundaries; enforce sensitive behavior with backend validation/interceptors too.",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this rule. Prefer kebab-case or camelCase such as 'show-archive-reason'.",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name for this rule, used by designers/documentation."
},
"condition": {
"type": "object",
"description": "The condition that triggers this rule. For isEmpty/isNotEmpty, omit value.",
"properties": {
"fieldId": {
"type": "string",
"description": "The field whose value is evaluated. Must match a field id in the same form.",
"minLength": 1
},
"operator": {
"type": "string",
"enum": ["equals", "notEquals", "isEmpty", "isNotEmpty"],
"description": "Comparison operator. Use equals/notEquals with value; use isEmpty/isNotEmpty without value."
},
"value": {
"description": "The value to compare against (not used for isEmpty/isNotEmpty)"
}
},
"required": ["fieldId", "operator"],
"additionalProperties": false
},
"actions": {
"type": "array",
"description": "Actions to perform when the condition is met. Actions are executed in order.",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["hide", "show", "disable", "enable", "setValue"],
"description": "The action type. hide/show/disable/enable target fields or groups; setValue targets fields."
},
"targetType": {
"type": "string",
"enum": ["field", "group"],
"description": "Whether the target is a field or a group."
},
"targetId": {
"type": "string",
"description": "The id of the target field or group. Must exist in this form's fields or layout groups.",
"minLength": 1
},
"value": {
"description": "The value to set. Used only for setValue actions."
}
},
"required": ["type", "targetType", "targetId"],
"additionalProperties": false
}
}
},
"required": ["id", "condition", "actions"],
"additionalProperties": false,
"examples": [
{
"id": "show-archive-reason",
"name": "Show archive reason",
"condition": { "fieldId": "status", "operator": "equals", "value": 3 },
"actions": [
{ "type": "show", "targetType": "field", "targetId": "archiveReason" }
]
}
]
}

24
schemas/low-code/definitions/interceptor-type.schema.json

@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "interceptor-type.schema.json",
"title": "InterceptorType",
"description": "When an entity command interceptor runs. Prefer PascalCase values shown here.",
"markdownDescription": "`Pre` runs before the command is persisted and can block with `globalError`. `Post` runs after the command succeeds and is best for logging, notifications, publishing events, or enqueueing jobs. `Replace` is reserved for replacing command behavior where supported and should be avoided unless the host explicitly supports it.",
"type": "string",
"enum": [
"Pre",
"pre",
"Post",
"post",
"Replace",
"replace"
],
"enumDescriptions": [
"Before persistence; can block with globalError.",
"Before persistence; can block with globalError.",
"After successful persistence; use for side effects.",
"After successful persistence; use for side effects.",
"Replace command behavior where supported.",
"Replace command behavior where supported."
]
}

50
schemas/low-code/definitions/page-column-descriptor.schema.json

@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-column-descriptor.schema.json",
"title": "PageColumnDescriptor",
"description": "Describes a property displayed by an entity page list, card, calendar event, or gallery item.",
"markdownDescription": "AI guidance: use page columns/card fields to control runtime page visibility. Each `propertyName` must match a property on the page entity. Keep this separate from entity property definitions so the same entity can have different views on different pages.",
"type": "object",
"properties": {
"propertyName": {
"type": "string",
"description": "Property displayed by this column/card field. Must match a property on the page entityName.",
"minLength": 1
},
"label": {
"type": "string",
"description": "Optional page-specific label override. Omit to use the property displayName/name.",
"minLength": 1
},
"order": {
"type": "integer",
"description": "Display order. Lower values appear first.",
"default": 0
},
"exportOrder": {
"type": "integer",
"description": "Optional export order. Lower values appear first in Excel, CSV, download-link columns, and file bundles. Omit to reuse display order."
},
"width": {
"type": "string",
"description": "Optional CSS width for tabular columns, for example '160px', '12rem', or '20%'.",
"minLength": 1
},
"visible": {
"type": "boolean",
"description": "Whether the field is visible by default on this page.",
"default": true
},
"exportable": {
"type": "boolean",
"description": "Whether the field can be exported from this page.",
"default": true
}
},
"required": ["propertyName"],
"additionalProperties": false,
"examples": [
{ "propertyName": "Title", "label": "Title", "order": 0, "exportOrder": 0, "width": "240px", "visible": true },
{ "propertyName": "InternalNotes", "visible": false, "exportable": false }
]
}

260
schemas/low-code/definitions/page-descriptor.schema.json

@ -0,0 +1,260 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-descriptor.schema.json",
"title": "PageDescriptor",
"description": "Describes a runtime UI page. Pages create menu items and choose how an entity or dashboard is rendered.",
"markdownDescription": "AI guidance: `name` is the stable route/menu key and should be URL-safe kebab-case (for example `customers` or `event-calendar`). `title` is user-facing. For entity pages, set `entityName` to an existing entity. Configure list/gallery/card visibility with `columns`, filtering with `filters`, and create/edit UI with form names. For `dashboard` pages, provide `dashboard` and omit entity-only fields unless intentionally supported by the runtime. Do not put page-only settings on entity properties.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Stable URL-safe page identifier. Prefer kebab-case, for example 'customers', 'event-calendar', or 'sales-dashboard'. This becomes part of the runtime route and is referenced by saveSuccessPageName.",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title for the menu item and page header.",
"minLength": 1
},
"icon": {
"type": "string",
"description": "FontAwesome icon class for the menu item, for example 'fa-solid fa-users'."
},
"type": {
"$ref": "page-type.schema.json"
},
"entityName": {
"type": "string",
"description": "Full name of the root entity this page displays. Required for dataGrid, kanban, calendar, gallery, and form pages. Usually omitted for dashboard pages.",
"minLength": 1
},
"groupByProperty": {
"type": "string",
"description": "Property name to group entities by. Required for kanban pages. Must reference a property on entityName, preferably an enum/status property with a small fixed set of values.",
"minLength": 1
},
"calendarStartProperty": {
"type": "string",
"description": "Date or DateTime property used as the start date for calendar pages. Required for calendar pages.",
"minLength": 1
},
"calendarEndProperty": {
"type": "string",
"description": "Optional Date or DateTime property used as the end date for calendar pages. Use when events can span a date range.",
"minLength": 1
},
"calendarTimeProperty": {
"type": "string",
"description": "Optional Time property used as the start time for calendar pages when the start date is date-only or time is stored separately.",
"minLength": 1
},
"calendarDurationProperty": {
"type": "string",
"description": "Optional numeric property used as duration in minutes for calendar pages. Use this instead of calendarEndProperty when records store duration.",
"minLength": 1
},
"galleryImageProperty": {
"type": "string",
"description": "Optional Image property used as the cover image for gallery pages. The property should have entity property type image.",
"minLength": 1
},
"defaultSortProperty": {
"type": "string",
"description": "Optional property used as the default sorting field when the client does not send explicit sorting. Must reference a property on entityName.",
"minLength": 1
},
"defaultSortDescending": {
"type": "boolean",
"description": "Whether the default sort property is sorted descending.",
"default": false
},
"defaultFileExportMode": {
"type": "integer",
"enum": [0, 1, 2],
"description": "Default file export mode for file/image columns. 0 exports file names, 1 exports metadata columns, and 2 exports download-link columns."
},
"allowFileBundleExport": {
"type": "boolean",
"description": "Whether file bundle export is available for this page.",
"default": true
},
"columns": {
"type": "array",
"description": "Page-owned column/card field configuration. Use for dataGrid, kanban card fields, calendar event fields, and gallery card fields. Each propertyName must reference a property on entityName.",
"items": {
"$ref": "page-column-descriptor.schema.json"
}
},
"filters": {
"type": "array",
"description": "Page-owned filter configuration for filterable page types. Each propertyName must reference a property on entityName. Use this instead of legacy property-level UI filter settings.",
"items": {
"$ref": "page-filter-descriptor.schema.json"
}
},
"order": {
"type": "integer",
"description": "Menu sort order (lower values appear first)",
"default": 0
},
"formName": {
"type": "string",
"description": "Name of the form to render. Required when type is 'form'. Must match a form descriptor whose entityName matches this page entityName.",
"minLength": 1
},
"createFormName": {
"type": "string",
"description": "Name of the form to use when creating records from dataGrid, kanban, calendar, or gallery pages. Must match a form descriptor for the same entity."
},
"editFormName": {
"type": "string",
"description": "Name of the form to use when editing records from dataGrid, kanban, calendar, or gallery pages. Must match a form descriptor for the same entity."
},
"createFormDisplay": {
"type": "string",
"enum": ["modal", "Modal", "page", "Page"],
"description": "How to display the create form. Use 'modal' for quick CRUD; use 'page' for longer forms or when deep links are desired.",
"default": "modal"
},
"editFormDisplay": {
"type": "string",
"enum": ["modal", "Modal", "page", "Page"],
"description": "How to display the edit form. Use 'modal' for quick CRUD; use 'page' for longer forms or when deep links are desired.",
"default": "modal"
},
"saveSuccessNavigation": {
"type": "string",
"enum": ["stay", "page", "url"],
"description": "Navigation behavior after a standalone form page saves successfully. 'stay' keeps the user on the form page; 'page' navigates to saveSuccessPageName; 'url' navigates to saveSuccessUrl.",
"default": "stay"
},
"saveSuccessPageName": {
"type": "string",
"description": "Existing page name to open after a form page saves successfully when saveSuccessNavigation is 'page'.",
"minLength": 1
},
"saveSuccessUrl": {
"type": "string",
"description": "URL or application path to open after a form page saves successfully when saveSuccessNavigation is 'url'.",
"minLength": 1
},
"dashboard": {
"$ref": "dashboard-descriptor.schema.json",
"description": "Dashboard layout and visualizations. Required only when type is dashboard."
},
"group": {
"type": "string",
"description": "Name of the page group this page belongs to. Must match pageGroups[].name. Omit for root-level pages.",
"minLength": 1
},
"permissionConfig": {
"$ref": "page-permission-config.schema.json",
"description": "Optional page operation permission overrides. Omit operations to use generated defaults."
}
},
"required": ["name", "title", "type"],
"allOf": [
{
"if": {
"properties": { "type": { "enum": ["dataGrid", "DataGrid"] } }
},
"then": {
"required": ["entityName"]
}
},
{
"if": {
"properties": { "type": { "enum": ["kanban", "Kanban"] } }
},
"then": {
"required": ["entityName", "groupByProperty"]
}
},
{
"if": {
"properties": { "type": { "enum": ["calendar", "Calendar"] } }
},
"then": {
"required": ["entityName", "calendarStartProperty"]
}
},
{
"if": {
"properties": { "type": { "enum": ["gallery", "Gallery"] } }
},
"then": {
"required": ["entityName"]
}
},
{
"if": {
"properties": { "type": { "enum": ["form", "Form"] } }
},
"then": {
"required": ["entityName", "formName"]
}
},
{
"if": {
"properties": { "type": { "enum": ["dashboard", "Dashboard"] } }
},
"then": {
"required": ["dashboard"]
}
}
],
"additionalProperties": false,
"examples": [
{
"name": "events",
"title": "Events",
"type": "dataGrid",
"entityName": "Acme.Events.Event",
"columns": [
{ "propertyName": "Title", "order": 0 },
{ "propertyName": "Status", "order": 1 }
],
"filters": [
{ "propertyName": "Title", "control": "text", "defaultOperator": "contains" }
],
"createFormName": "event-form",
"editFormName": "event-form"
},
{
"name": "event-calendar",
"title": "Event Calendar",
"type": "calendar",
"entityName": "Acme.Events.Event",
"calendarStartProperty": "StartDate",
"calendarEndProperty": "EndDate"
},
{
"name": "event-dashboard",
"title": "Event Dashboard",
"type": "dashboard",
"dashboard": {
"rows": [
{
"items": [
{
"name": "events-by-status",
"type": "chart",
"title": "Events by Status",
"entityName": "Acme.Events.Event",
"chart": {
"chartType": "bar",
"xAxis": { "property": "Status" },
"yAxis": [{ "aggregation": "count", "label": "Events" }]
}
}
]
}
]
}
}
]
}

119
schemas/low-code/definitions/page-filter-descriptor.schema.json

@ -0,0 +1,119 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-filter-descriptor.schema.json",
"title": "PageFilterDescriptor",
"description": "Describes a runtime filter control for an entity page.",
"markdownDescription": "AI guidance: each filter must point to a page entity property. Use `control: \"auto\"` unless a specific control is needed. Use text/contains for string search, range/between for numeric values, dateRange/timeRange for temporal values, select/multiSelect for enums, lookup for foreign keys, and exists for null checks. Do not use the legacy `operator` property; use `defaultOperator`.",
"type": "object",
"properties": {
"propertyName": {
"type": "string",
"description": "Property filtered by this control. Must match a property on the page entityName.",
"minLength": 1
},
"label": {
"type": "string",
"description": "Optional page-specific label override. Omit to use the property displayName/name.",
"minLength": 1
},
"order": {
"type": "integer",
"description": "Filter display order. Lower values appear first.",
"default": 0
},
"visible": {
"type": "boolean",
"description": "Whether the filter is shown at runtime",
"default": true
},
"control": {
"type": "string",
"description": "Runtime control used by the filter. 'auto' selects based on property type; choose explicit controls for predictable generated UI.",
"enum": ["auto", "text", "range", "dateRange", "timeRange", "select", "multiSelect", "lookup", "exists"],
"default": "auto"
},
"defaultOperator": {
"type": "string",
"description": "Default operator applied by this filter. Use 'contains' for text search, 'equal' for enum/FK exact matches, 'between' for ranges, and 'default' to let runtime choose.",
"enum": [
"default",
"equal",
"notEqual",
"contains",
"notContains",
"startsWith",
"endsWith",
"greaterThan",
"greaterThanOrEqual",
"lessThan",
"lessThanOrEqual",
"between",
"hasValue",
"isNull",
"isNotNull",
"in",
"notIn"
]
},
"allowedOperators": {
"type": "array",
"description": "Operators the runtime filter UI allows users to choose. Omit to let the runtime choose based on property type/control.",
"items": {
"type": "string",
"enum": [
"default",
"equal",
"notEqual",
"contains",
"notContains",
"startsWith",
"endsWith",
"greaterThan",
"greaterThanOrEqual",
"lessThan",
"lessThanOrEqual",
"between",
"hasValue",
"isNull",
"isNotNull",
"in",
"notIn"
]
},
"uniqueItems": true
},
"defaultValue": {
"description": "Optional default filter value applied when the page first loads. Shape depends on control/operator."
},
"placeholder": {
"type": "string",
"description": "Optional runtime input placeholder"
},
"helpText": {
"type": "string",
"description": "Optional runtime help text"
},
"allowMultipleValues": {
"type": "boolean",
"description": "Whether the filter can submit multiple values. Useful with multiSelect/in/notIn.",
"default": false
},
"clearable": {
"type": "boolean",
"description": "Whether runtime users can clear the filter.",
"default": true
},
"initiallyExpanded": {
"type": "boolean",
"description": "Whether the runtime filter panel opens expanded.",
"default": false
}
},
"required": ["propertyName"],
"additionalProperties": false,
"examples": [
{ "propertyName": "Title", "control": "text", "defaultOperator": "contains", "placeholder": "Search title" },
{ "propertyName": "Status", "control": "select", "defaultOperator": "equal", "clearable": true },
{ "propertyName": "StartDate", "control": "dateRange", "defaultOperator": "between" }
]
}

44
schemas/low-code/definitions/page-group-descriptor.schema.json

@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-group-descriptor.schema.json",
"title": "PageGroupDescriptor",
"description": "Describes a menu group/folder that can contain pages and sub-groups.",
"markdownDescription": "AI guidance: create page groups when several pages belong to the same feature area. Pages reference groups by `group`. Nested groups reference parent groups by `parent`. Use stable kebab-case names and concise user-facing titles.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique URL-safe identifier for the group. Prefer kebab-case, for example 'crm' or 'content-studio'.",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title for the menu group.",
"minLength": 1
},
"icon": {
"type": "string",
"description": "FontAwesome icon class, for example 'fa-solid fa-folder' or 'fa-solid fa-calendar-days'."
},
"order": {
"type": "integer",
"description": "Sort order within the parent level (lower values appear first)",
"default": 0
},
"parent": {
"type": "string",
"description": "Name of the parent group for nesting. Must match another pageGroups[].name. Omit for root-level groups.",
"minLength": 1
}
},
"required": ["name", "title"],
"additionalProperties": false,
"examples": [
{ "name": "events", "title": "Events", "icon": "fa-solid fa-calendar-days", "order": 10 },
{ "name": "event-admin", "title": "Admin", "parent": "events", "order": 20 }
]
}

27
schemas/low-code/definitions/page-permission-config.schema.json

@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-permission-config.schema.json",
"title": "PagePermissionConfig",
"description": "Permission configuration for a page's operations. Only overridden values are stored; omitted values use auto-generated defaults based on the page.",
"markdownDescription": "AI guidance: each operation can be `default`, `public`, `authenticated`, or a permission name. Omit an operation to use runtime-generated defaults. Use `public` only for anonymous pages. Use `authenticated` for pages any logged-in user can view. Use explicit permission names for controlled business operations.",
"type": "object",
"properties": {
"view": {
"type": "string",
"description": "Permission for viewing the page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default."
},
"create": {
"type": "string",
"description": "Permission for create operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default."
},
"update": {
"type": "string",
"description": "Permission for update operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default."
},
"delete": {
"type": "string",
"description": "Permission for delete operation on this page. Can be a specific permission name, 'default', 'public', or 'authenticated'. Omit to use auto-generated default."
}
},
"additionalProperties": false
}

36
schemas/low-code/definitions/page-type.schema.json

@ -0,0 +1,36 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "page-type.schema.json",
"title": "PageType",
"description": "The runtime page renderer to use. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.",
"markdownDescription": "`dataGrid` renders searchable/filterable tabular CRUD for an entity. `kanban` renders grouped cards and requires `groupByProperty`. `calendar` renders entity records on a calendar and requires `calendarStartProperty`. `gallery` renders visual cards and may use `galleryImageProperty`. `form` renders a standalone create/edit form page and requires `formName`. `dashboard` renders dashboard rows/visualizations and requires `dashboard`.",
"type": "string",
"enum": [
"dataGrid",
"DataGrid",
"kanban",
"Kanban",
"calendar",
"Calendar",
"form",
"Form",
"dashboard",
"Dashboard",
"gallery",
"Gallery"
],
"enumDescriptions": [
"Entity data grid/list page.",
"Entity data grid/list page.",
"Entity kanban board grouped by an enum/status property.",
"Entity kanban board grouped by an enum/status property.",
"Entity calendar page using date/time properties.",
"Entity calendar page using date/time properties.",
"Standalone form page bound to a form descriptor.",
"Standalone form page bound to a form descriptor.",
"Dashboard page with chart/list/number visualizations.",
"Dashboard page with chart/list/number visualizations.",
"Entity gallery/card page, optionally image-backed.",
"Entity gallery/card page, optionally image-backed."
]
}

44
schemas/low-code/definitions/permission-descriptor.schema.json

@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "permission-descriptor.schema.json",
"title": "PermissionDescriptor",
"description": "Defines a custom permission created through the designer. Supports parent-child hierarchy.",
"markdownDescription": "AI guidance: use stable dot-separated permission names, for example `Acme.Events.Create`. Define a parent permission for the feature and child permissions for operations. Pages and endpoints can reference these names. Permission definitions only declare permissions; roles/users must still be granted permissions through permission management.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique dot-separated permission name, for example 'Acme.Events.Create'.",
"minLength": 1
},
"displayName": {
"type": "string",
"description": "Human-readable display name shown in permission management UI.",
"minLength": 1
},
"children": {
"type": "array",
"description": "Child permissions forming a hierarchy. Use for feature -> operation grouping.",
"items": {
"$ref": "#"
}
}
},
"required": ["name", "displayName"],
"additionalProperties": false,
"examples": [
{
"name": "Acme.Events",
"displayName": "Events",
"children": [
{ "name": "Acme.Events.Create", "displayName": "Create events" },
{ "name": "Acme.Events.Update", "displayName": "Update events" },
{ "name": "Acme.Events.Delete", "displayName": "Delete events" }
]
}
]
}

36
schemas/low-code/definitions/script-background-job-descriptor.schema.json

@ -0,0 +1,36 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "script-background-job-descriptor.schema.json",
"title": "Script Background Job Descriptor",
"description": "Defines a named JavaScript background job handler.",
"markdownDescription": "AI guidance: use background jobs for asynchronous work that is explicitly enqueued by code/scripts, such as notifications, imports, or long-running recalculations. `name` is the job identifier used by enqueue calls. Keep scripts retry-safe and idempotent because background jobs may run more than once after failures.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique identifier for the background job. Scripts/enqueuers reference this name.",
"minLength": 1
},
"javascript": {
"type": "string",
"description": "JavaScript code to execute when the job is enqueued/run. Use context payload/job arguments where available and keep retry-safe.",
"minLength": 1
},
"description": {
"type": "string",
"description": "Optional description for designer documentation and model health context."
}
},
"required": ["name", "javascript"],
"additionalProperties": false,
"examples": [
{
"name": "SendEventReminder",
"javascript": "context.log('Sending event reminder job.');"
}
]
}

70
schemas/low-code/definitions/script-background-worker-descriptor.schema.json

@ -0,0 +1,70 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "script-background-worker-descriptor.schema.json",
"title": "Script Background Worker Descriptor",
"description": "Defines a scheduled JavaScript background worker.",
"markdownDescription": "AI guidance: use background workers for recurring scheduled work. Provide either `period` in milliseconds or `cronExpression`; do not provide both unless the host explicitly chooses one. Keep scripts idempotent and short. Use workers for polling, cleanup, synchronization, or recurring summary generation.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique identifier for the background worker.",
"minLength": 1
},
"period": {
"type": "integer",
"description": "Execution period in milliseconds. Example: 300000 for every 5 minutes.",
"minimum": 1
},
"cronExpression": {
"type": ["string", "null"],
"description": "Cron expression for scheduler-backed providers. Omit it, set it to null, or leave it empty when the worker uses period-based scheduling."
},
"javascript": {
"type": "string",
"description": "JavaScript code to execute when the worker runs. Keep idempotent; workers may overlap or retry depending on scheduler configuration.",
"minLength": 1
},
"description": {
"type": "string",
"description": "Optional description for designer documentation and model health context."
}
},
"required": ["name", "javascript"],
"anyOf": [
{
"required": ["period"],
"not": {
"required": ["cronExpression"],
"properties": {
"cronExpression": {
"type": "string",
"minLength": 1
}
}
}
},
{
"required": ["cronExpression"],
"not": { "required": ["period"] },
"properties": {
"cronExpression": {
"type": "string",
"minLength": 1
}
}
}
],
"additionalProperties": false,
"examples": [
{
"name": "CleanupExpiredEvents",
"period": 3600000,
"javascript": "context.log('Running expired event cleanup.');"
}
]
}

42
schemas/low-code/definitions/script-event-handler-descriptor.schema.json

@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "script-event-handler-descriptor.schema.json",
"title": "Script Event Handler Descriptor",
"description": "Defines a named JavaScript handler for a distributed event name.",
"markdownDescription": "AI guidance: use event handlers for asynchronous reactions to domain/application events. `eventName` must match the event name published by the application or another script. Keep handlers idempotent because distributed events can be retried. Scripts can use the host-provided context, including event payload/request data where available, db, current tenant/user, email, logging, event publishing, and job enqueue helpers.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "Optional schema reference used when this descriptor is stored as a model descriptor file."
},
"name": {
"type": "string",
"description": "Unique identifier for the handler. Prefer a descriptive name such as 'NotifyWhenEventCompleted'.",
"minLength": 1
},
"eventName": {
"type": "string",
"description": "Distributed event name to subscribe to. Must exactly match the publisher's event name.",
"minLength": 1
},
"javascript": {
"type": "string",
"description": "JavaScript code to execute when the event is received. Keep idempotent; avoid assuming one-time delivery.",
"minLength": 1
},
"description": {
"type": "string",
"description": "Optional description for designer documentation and model health context."
}
},
"required": ["name", "eventName", "javascript"],
"additionalProperties": false,
"examples": [
{
"name": "NotifyEventCompleted",
"eventName": "Acme.Events.EventCompleted",
"javascript": "context.log('Event completed handler executed.');"
}
]
}

96
schemas/low-code/definitions/validator-descriptor.schema.json

@ -0,0 +1,96 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "validator-descriptor.schema.json",
"title": "ValidatorDescriptor",
"description": "A single backend/UI validator applied to an entity property or form field.",
"markdownDescription": "AI guidance: always set `type`. Use `required` for mandatory input, `minLength`/`maxLength`/`stringLength` for strings, `min`/`max`/`range` for numbers and comparable values, `pattern` or `regularExpression` for regex, and `email`, `phone`, `url`, or `creditCard` for common formats. Use lower-case validator names for new JSON. Add `message` only when a custom localized/user-facing message is needed.",
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"description": "Validator type. Prefer lower-case values in descriptor JSON; PascalCase aliases are accepted for compatibility.",
"enum": [
"required",
"Required",
"minLength",
"MinLength",
"maxLength",
"MaxLength",
"stringLength",
"StringLength",
"min",
"Min",
"minimum",
"Minimum",
"max",
"Max",
"maximum",
"Maximum",
"range",
"Range",
"pattern",
"Pattern",
"regularExpression",
"RegularExpression",
"email",
"Email",
"emailAddress",
"EmailAddress",
"phone",
"Phone",
"url",
"Url",
"creditCard",
"CreditCard"
]
},
"message": {
"type": "string",
"description": "Optional custom error message shown when validation fails. Omit to use the default localized message."
},
"length": {
"type": "integer",
"description": "Length value for minLength or maxLength validators. Example: { \"type\": \"maxLength\", \"length\": 128 }.",
"minimum": 0
},
"minimumLength": {
"type": "integer",
"description": "Minimum length for stringLength validator.",
"minimum": 0
},
"maximumLength": {
"type": "integer",
"description": "Maximum length for stringLength validator.",
"minimum": 0
},
"value": {
"type": "number",
"description": "Generic numeric value alias for single-value validators such as min/minimum, max/maximum, minLength/maxLength, and stringLength maximumLength."
},
"minimum": {
"type": "number",
"description": "Minimum value for min/minimum or range validators."
},
"maximum": {
"type": "number",
"description": "Maximum value for max/maximum or range validators."
},
"pattern": {
"type": "string",
"description": "Regular expression pattern for pattern/regularExpression validators. Store the regex pattern string only; do not include leading/trailing slashes."
},
"allowEmptyStrings": {
"type": "boolean",
"description": "Whether required validation should allow empty strings. Usually false for user-entered text."
}
},
"additionalProperties": true,
"examples": [
{ "type": "required" },
{ "type": "maxLength", "length": 128 },
{ "type": "range", "minimum": 0, "maximum": 100 },
{ "type": "pattern", "pattern": "^[A-Z]{3}-[0-9]{4}$", "message": "Code must match ABC-1234." },
{ "type": "email" }
]
}

19
schemas/low-code/manifest.json

@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"name": "ABP Low-Code Model Descriptor Schemas",
"version": "10.5",
"publicBaseUrl": "https://raw.githubusercontent.com/abpframework/abp/rel-10.5/schemas/low-code",
"definitionsPath": "definitions",
"descriptorSchemas": {
"enums": "definitions/enum-descriptor.schema.json",
"entities": "definitions/entity-descriptor.schema.json",
"endpoints": "definitions/endpoint-descriptor.schema.json",
"eventHandlers": "definitions/script-event-handler-descriptor.schema.json",
"backgroundJobs": "definitions/script-background-job-descriptor.schema.json",
"backgroundWorkers": "definitions/script-background-worker-descriptor.schema.json",
"pageGroups": "definitions/page-group-descriptor.schema.json",
"pages": "definitions/page-descriptor.schema.json",
"forms": "definitions/form-descriptor.schema.json",
"permissions": "definitions/permission-descriptor.schema.json"
}
}
Loading…
Cancel
Save