> For the complete documentation index, see [llms.txt](https://docs.gridmate.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gridmate.io/advanced-guides/enhanced-record-layout.md).

# Enhanced Record Layout

## Introduction

GridMate ships three record layout components in the App Builder palette:

* **GM - Record Layout** — the original Aura component. This component is able to display any object supported by UI-API.
* **GM - Record Layout (LWC)** — the Aura-wrapped Lightning Web Component. This component doesn't require UI-API.&#x20;
* **GM - Enhanced Record Layout** — a native Lightning Web Component. Drop it on a record, app, home or Experience Cloud page, on desktop and on phone, no wrapper needed. This component doesn't require UI-API.

All three read the same **Record Layout** JSON: sections, rows, columns, visibility, read-only and coloring rules. See [Record Layout (LWC)](/product-tour/record-layout-lwc.md) for the full description of that JSON. This guide covers what **GM - Enhanced Record Layout** adds on top of it:

* **Layout Actions** — buttons in the header that reshape the layout at runtime.
* **Validation Rules** — warnings and errors evaluated while the user types, or on save.
* **Apex Validation Action** — a button that runs your own Apex validator against the unsaved record.
* **Layout Config** — load the whole configuration from a custom metadata record and reuse it on every page.

The properties of the component are listed in [GM - Enhanced Record Layout](/package-reference/components-library/gm-enhanced-record-layout.md).

## Layout Actions

Set the **Layout Actions** property to a JSON array. Each action is rendered as a button in the header of the component. A layout action is a toggle: click it to apply a set of changes to the layout, click it again to revert them.

The example below adds two buttons to an Account layout: **Hide Empty** hides the fields that have no value, **Highlight Required** colors the required fields.

```json
[
    {
        "name": "hideEmpty",
        "label": "Hide Empty",
        "icon": "utility:hide",
        "mode": "view",
        "command": {
            "id": "hide-empty",
            "target": { "formula": "empty" },
            "changes": { "hidden": true }
        }
    },
    {
        "name": "highlightRequired",
        "label": "Highlight Required",
        "icon": "utility:warning",
        "command": {
            "id": "highlight-required",
            "target": { "formula": "required" },
            "changes": { "backgroundColor": "var(--slds-g-color-error-base-80)" }
        }
    }
]
```

* `mode` — `view` or `edit`. Omit it to show the button in both modes.
* `command.target` — the elements to change: `{ "fields": ["Phone", "Fax"] }` for a list of fields, `{ "type": "section", "sections": ["addressInformation"] }` for a list of sections, or `{ "formula": "..." }` for a [Javascript Formula](/package-reference/javascript-formulas.md) evaluated against every field. The formula can use the record fields as well as `value`, `empty`, `required` and `updateable`.
* `command.changes` — the changes to apply: `hidden`, `readOnly`, `highlighted`, `backgroundColor` or `style`.

{% hint style="info" %}
An action without a `command` is dispatched to the parent component as a `layoutaction` event, so a custom Lightning Web Component wrapping the layout can handle it.
{% endhint %}

## Validate before you save

Add a `validate` array to any field of the **Record Layout** JSON. Each rule has an expression, written with the same syntax as `visibility` and `readOnly`, a message and a type:

* `"type": "warning"` — while the expression is true, an amber note is displayed under the field. The user can still save.
* `"type": "error"` — while the expression is true, a red note is displayed under the field and the field is required. The record cannot be saved until the field is filled in.

Section headers display the number of required fields of the section.

```json
{
    "apiName": "Phone",
    "validate": [
        {
            "exp": { "AccountSource": { "operator": "=", "value": "Web" } },
            "message": "Web accounts must have a phone number.",
            "type": "error"
        }
    ]
}
```

By default the rules are evaluated immediately, as the user types. Set `rulesMode` at the top level of the **Record Layout** JSON to evaluate them when the user clicks **Save** instead:

```json
{
    "density": "comfy",
    "rulesMode": "onsave",
    "sections": [...]
}
```

## Apex validation action

When the validation needs data that is not on the record (related records, an external system, a complex business rule), add a layout action of type `apex`. The button sends the record, with its unsaved values, to your Apex class and displays the errors it returns.

### Layout action

```json
{
    "name": "validateAccount",
    "label": "Validate",
    "icon": "utility:check",
    "type": "apex",
    "mode": "edit",
    "apexClass": "AccountLayoutValidator",
    "params": { "strict": true },
    "successMessage": "Account looks good"
}
```

### Apex class

The class implements the `Callable` interface. GridMate calls `call('validate', args)` where `args` holds the `record` (the record with the values currently displayed), the `objectName` and the `params` of the action. The method returns a list of `gmpkg.SaveHookManager.SaveHookResult`:

* A result with `fields` is displayed as an error under each of these fields.
* A result without `fields` is displayed as a record-level error at the top of the layout.
* An empty list displays the `successMessage` toast.

```java
global with sharing class AccountLayoutValidator implements Callable {
    global Object call(String action, Map<String, Object> args) {
        Account acc = (Account) args.get('record');
        Map<String, Object> params = (Map<String, Object>) args.get('params');
        Boolean strict = params != null && params.get('strict') == true;

        List<gmpkg.SaveHookManager.SaveHookResult> results = new List<gmpkg.SaveHookManager.SaveHookResult>();

        // Field-level error: displayed under the Phone field
        if (acc.AccountSource == 'Web' && String.isBlank(acc.Phone)) {
            results.add(
                new gmpkg.SaveHookManager.SaveHookResult(
                    'FIELD_CUSTOM_VALIDATION_EXCEPTION',
                    'Web accounts must have a phone number.',
                    new List<String>{ 'Phone' }
                )
            );
        }

        // Record-level error: displayed at the top of the layout
        if (strict && acc.Industry == 'Banking' && String.isBlank(acc.Description)) {
            results.add(
                new gmpkg.SaveHookManager.SaveHookResult(
                    'CUSTOM_VALIDATION_EXCEPTION',
                    'Banking accounts require a description before they can be approved.'
                )
            );
        }

        return results;
    }
}
```

{% hint style="info" %}
The validation action is advisory: **Save** does not run the validator again. To enforce the rules on save, register the same class as a save hook. See [Save Hook Framework](/advanced-guides/save-hook-framework.md).
{% endhint %}

## Configure once, reuse everywhere

Instead of pasting the JSON in every page, store the configuration in a **Record Layout** custom metadata record and reference it from the component.

{% stepper %}
{% step %}

### Create the Record Layout record

In **Setup**, open **Custom Metadata Types**, click **Manage Records** next to **Record Layout** and click **New**.

<table><thead><tr><th width="253.546875">Field</th><th>Description</th></tr></thead><tbody><tr><td><strong>Sobject</strong></td><td>The object of the record to display.</td></tr><tr><td><strong>Record Id Field</strong></td><td>The field of the page record holding the Id of the record to display. <code>Id</code> when the component is placed on the record itself.</td></tr><tr><td><strong>Record Layout</strong></td><td>The <strong>Record Layout</strong> JSON.</td></tr><tr><td><strong>Layout Actions</strong></td><td>The <strong>Layout Actions</strong> JSON.</td></tr><tr><td><strong>Record Actions</strong></td><td>The <strong>Record Actions</strong> JSON.</td></tr><tr><td><strong>Button Actions</strong></td><td>Display the record actions as buttons instead of icons.</td></tr><tr><td><strong>Visible Actions</strong></td><td>Number of record actions displayed before the overflow menu.</td></tr><tr><td><strong>Show Border</strong></td><td>Display the card border.</td></tr></tbody></table>
{% endstep %}

{% step %}

### Reference it from the component

In the App Builder, set the **Layout Config** property of **GM - Enhanced Record Layout** to the **Record Layout Name** of the record. Leave the other properties empty: the values of the metadata record replace the properties set in the App Builder, including the empty ones.
{% endstep %}
{% endstepper %}
