<a id="layout-json-format"></a>
<a id="id1"></a>
# Layout JSON Format

The structure of a custom object layout is defined using JSON. If no JSON is provided (empty), all properties are displayed in a simple vertical list.

This page is the reference for the JSON schema. For an overview of how layouts are managed and assigned to users, see [Custom Object Layouts](index.html.md#custom-objects-layouts). For the list of built-in renderers and their options, see [Layout Renderers Reference](renderers_catalog.html.md#renderers-catalog).

<a id="node-types"></a>
## Node Types

The layout JSON consists of nested nodes. Each node must have a `type` property. The available node types are:

**Container Nodes** (can contain other nodes):

- `VerticalLayout` - Arranges children vertically, one below another
- `HorizontalLayout` - Arranges children horizontally in columns
- `Section` - A collapsible section with a header label
- `Tabs` - Organizes children as tabbed panels

**Leaf Nodes** (display content):

- `Field` - Displays a property field. Optionally accepts a `renderer` to swap the default input for a specialised widget (see [Renderers](#renderers)).
- `Content` - Displays static HTML content (headings, text, separators, etc.)

<a id="basic-structure"></a>
## Basic Structure

A simple vertical layout with three fields:

```json
{
  "type": "VerticalLayout",
  "children": [
    {"type": "Field", "fieldId": "Name"},
    {"type": "Field", "fieldId": "Description"},
    {"type": "Field", "fieldId": "Status"}
  ]
}
```

<a id="field-node"></a>
## Field Node

Field nodes reference a property by name using the `fieldId` property:

```json
{"type": "Field", "fieldId": "PropertyName"}
```

The `fieldId` must match the name of an active property definition for the custom object. Fields not included in the layout will appear in an "Other Fields" section.

<a id="content-node"></a>
## Content Node

Content nodes display static HTML content such as headings, help text, or separators. Use the `html` property to specify the content:

```json
{"type": "Content", "html": "<h4>Section Title</h4><p>Enter details below.</p>"}
```

Content nodes can also be used as spacers in horizontal layouts by using an empty `html` value:

```json
{
  "type": "HorizontalLayout",
  "children": [
    {"type": "Field", "fieldId": "FirstName", "columns": 5},
    {"type": "Content", "html": "", "columns": 2},
    {"type": "Field", "fieldId": "LastName", "columns": 5}
  ]
}
```

In a `HorizontalLayout`, Content nodes must include a `columns` property like other children.

**Allowed HTML tags:** b, div, hr, i, u, li, sup, br, ol, ul, font, small, h1, h2, h3, h4, p, a, span, sub, em, table, thead, tbody, th, tr, td.

For safety, HTML content is sanitized before display. Script tags and other potentially unsafe content will be removed.

<a id="horizontal-layout"></a>
## Horizontal Layout

Use `HorizontalLayout` to arrange fields side by side. Each child must specify a `columns` value:

```json
{
  "type": "HorizontalLayout",
  "children": [
    {"type": "Field", "fieldId": "FirstName", "columns": 6},
    {"type": "Field", "fieldId": "LastName", "columns": 6}
  ]
}
```

The total columns should not exceed 12. In this example, both fields take up half the width (6 + 6 = 12).

<a id="section"></a>
## Section

Sections group related fields with a labeled header:

```json
{
  "type": "Section",
  "label": "Contact Information",
  "children": [
    {"type": "Field", "fieldId": "Email"},
    {"type": "Field", "fieldId": "Phone"}
  ]
}
```

<a id="tabs"></a>
## Tabs

Tabs organize content into tabbed panels. Each child must have a `label` property for the tab title:

```json
{
  "type": "Tabs",
  "children": [
    {
      "type": "VerticalLayout",
      "label": "General",
      "children": [
        {"type": "Field", "fieldId": "Name"},
        {"type": "Field", "fieldId": "Description"}
      ]
    },
    {
      "type": "VerticalLayout",
      "label": "Details",
      "children": [
        {"type": "Field", "fieldId": "Status"},
        {"type": "Field", "fieldId": "Priority"}
      ]
    }
  ]
}
```

<a id="read-only-fields"></a>
## Read-Only Fields

You can mark fields or entire containers as read-only by adding `"read_only": true` to a node. Read-only fields are displayed with a lock icon and cannot be edited by the user, even if they have edit permissions. This is useful for creating "review" layouts where most fields should be locked.

**Making a single field read-only:**

```json
{"type": "Field", "fieldId": "ApprovedBy", "read_only": true}
```

**Making all fields in a container read-only:**

```json
{
  "type": "Section",
  "label": "Approved Details",
  "read_only": true,
  "children": [
    {"type": "Field", "fieldId": "ApprovedBy"},
    {"type": "Field", "fieldId": "ApprovalDate"}
  ]
}
```

**Overriding a parent's read-only setting:**

A child node can explicitly set `"read_only": false` to override a parent container's read-only setting. In this example, the "Status" field is editable even though the parent container is read-only:

```json
{
  "type": "VerticalLayout",
  "read_only": true,
  "children": [
    {"type": "Field", "fieldId": "Name"},
    {"type": "Field", "fieldId": "Description"},
    {"type": "Field", "fieldId": "Status", "read_only": false}
  ]
}
```

The inheritance rules are:

- If a node has `"read_only": true`, it and all its descendants are read-only (unless overridden).
- If a node has `"read_only": false`, it explicitly overrides any inherited read-only state.
- If a node does not have a `read_only` property, it inherits from its parent.
- Content nodes do not support `read_only` (they have no editable content).

In the layout editor preview, read-only fields are shown with a "Read-only" badge and a grey background.

<a id="renderers"></a>
## Renderers

Each `Field` node in a layout normally uses the default input for the property's value type (textarea for Text, dropdown for Choice, native picker for Date, and so on). To replace the default with a specialised widget, set a `renderer` on the Field node:

```json
{
  "type": "Field",
  "fieldId": "Quantity",
  "renderer": "number-spinner"
}
```

With a renderer-specific override:

```json
{
  "type": "Field",
  "fieldId": "Quantity",
  "renderer": "number-spinner",
  "rendererOptions": {"step": 0.5}
}
```

Renderer-related properties on a Field node:

- `renderer` — the registered renderer name (e.g. `"number-spinner"`). Optional; when omitted the field is rendered using the default for its value type.
- `rendererOptions` — an optional object of renderer-specific settings. Most renderers take their data from the underlying property definition (`number-spinner` reads min/max/decimals from the numeric property; `transfer-list` reads values and separator from the Set property) and need no `rendererOptions` at all.

Most renderers restrict which property value types they support. `number-spinner` only works with `Numeric` properties, `radio-buttons` only with `Choice` properties, and `transfer-list` only with `Set` properties. Assigning a renderer to an incompatible field shows a validation error when the layout is saved.

A Field with a renderer behaves identically to a plain Field for everything else (read-only inheritance, "Other Fields" detection, columns in HorizontalLayout, etc.) — the only difference is *how* the value is presented and edited.

For a full list of available renderers and their options, see the [Layout Renderers Reference](renderers_catalog.html.md#renderers-catalog).

> **Note:** **Compound widgets that span several fields** (for example, a row that displays role and level together, or an editor that owns multiple related fields at once) are not yet supported. When the need is concrete, those will arrive as a separate node type rather than as a variation of `Field` — keeping single-field renderers and multi-field widgets cleanly distinct in the schema.

<a id="complex-example"></a>
## Complex Example

This example combines multiple layout features including tabs, sections, horizontal layouts, content, and a renderer override:

```json
{
  "type": "Tabs",
  "children": [
    {
      "type": "VerticalLayout",
      "label": "Overview",
      "children": [
        {
          "type": "Content",
          "html": "<h4>Basic Information</h4><p>Enter the core details for this item.</p>"
        },
        {
          "type": "HorizontalLayout",
          "children": [
            {"type": "Field", "fieldId": "Name", "columns": 8},
            {"type": "Field", "fieldId": "Status", "columns": 4}
          ]
        },
        {"type": "Field", "fieldId": "Description"}
      ]
    },
    {
      "type": "VerticalLayout",
      "label": "Details",
      "children": [
        {
          "type": "Section",
          "label": "Dates",
          "children": [
            {
              "type": "HorizontalLayout",
              "children": [
                {"type": "Field", "fieldId": "StartDate", "columns": 6},
                {"type": "Field", "fieldId": "EndDate", "columns": 6}
              ]
            }
          ]
        },
        {"type": "Content", "html": "<hr>"},
        {
          "type": "Section",
          "label": "Assignment",
          "children": [
            {"type": "Field", "fieldId": "AssignedTo"},
            {
              "type": "Field",
              "fieldId": "Priority",
              "renderer": "number-spinner"
            }
          ]
        }
      ]
    }
  ]
}
```

<a id="validation-rules"></a>
## Validation Rules

The layout JSON is validated when saving:

1. The root must be a valid node type (typically `VerticalLayout`, `Section`, or `Tabs`).
2. All `fieldId` values must match active property definition names.
3. `HorizontalLayout` children must have `columns` values between 1 and 12.
4. `HorizontalLayout` total columns should not exceed 12.
5. `Tabs` children must have `label` properties.
6. `read_only`, if present, must be a boolean (`true` or `false`).
7. `Content` nodes do not support `read_only`.
8. If a Field's `renderer` is set, it must name a registered renderer and the field's value type must be compatible with that renderer's allowed field types.
9. `rendererOptions`, if present, must be an object whose keys match the renderer's options schema.
