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. For the list of built-in renderers and their options, see Layout Renderers Reference.

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).

  • Content - Displays static HTML content (headings, text, separators, etc.)

Basic Structure

A simple vertical layout with three fields:

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

Field Node

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

{"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.

Content Node

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

{"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:

{
  "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.

Horizontal Layout

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

{
  "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).

Section

Sections group related fields with a labeled header:

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

Tabs

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

{
  "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"}
      ]
    }
  ]
}

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:

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

Making all fields in a container read-only:

{
  "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:

{
  "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.

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:

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

With a renderer-specific override:

{
  "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.

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.

Complex Example

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

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}

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.