> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom columns

> Name one key inside a structured input or output with a JSONPath expression, once per project, so tests, filters, and the records table can target it in monitoring and development

Tests evaluate a whole column. When your output is structured — for example, a
`transcription` object with a `summary` inside it — a test on the output column
measures the whole serialized object, not the key you care about.

A **custom column** gives one key a name. It is an
[RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) JSONPath expression that points into
the output or an input column. Once it exists, you use its name wherever a column name
goes: in tests, in filters, and in the records table. For example, a
[character length](https://docs.openlayer.com/tests/catalog/character-length.md) test on a custom column measures
`transcription.summary` alone.

A custom column belongs to the project. You define it once, and every inference pipeline
in the project and every [development](https://docs.openlayer.com/development/overview.md) commit use the same
definition, so tests in both monitoring and development mode can target it.

## Prerequisites

* Data in the project whose output, or one of whose input columns, holds a JSON object or
  array — or JSON stored as a string. That data can come from an inference pipeline or
  from a development commit. Path suggestions and the preview both read recent rows.
* The **Update projects** permission to create, edit, and delete custom columns. See
  [Roles and permissions](https://docs.openlayer.com/security/roles-and-permissions.md). Without it, the list is
  read-only.

## Create a custom column

Custom columns live under the project's **Settings** → **Custom columns**, one list for
the whole project. The **Add custom columns** entry in the records table's options menu,
on a pipeline or a commit, opens the same page.

The **New custom column** form asks for:

* **Name** — what tests and filters refer to. Use letters, numbers, spaces, underscores,
  and hyphens, starting with a letter or underscore. Names are unique per project, and a
  name can't start with `openlayer_` or match a column that already exists in the
  project's data.
* **Source column** — the column the path is evaluated against: the output
  (`openlayer_output`, the default) or an input column from any of the project's
  pipelines or development datasets. In development, `openlayer_output` is the column
  your [`openlayer.json`](https://docs.openlayer.com/development/openlayer-json#outputcolumnname) sets as
  `outputColumnName`.
* **Path** — a JSONPath relative to the source column, starting with `$`. Paths seen in
  recent data are offered as suggestions. Paths inside stringified JSON are not suggested,
  so type those by hand and check them with the preview.

### Example

Suppose each row's output looks like this:

```json theme={null}
{
  "transcription": [
    {
      "language": "en-GB",
      "summary": "The caller asked to move their appointment."
    },
    {
      "language": "fr-FR",
      "summary": "L'appelant a demandé à déplacer son rendez-vous."
    }
  ]
}
```

With **Source column** set to `openlayer_output`:

| Path | Value on this row |
| - | - |
| `$.transcription[0].summary` | `The caller asked to move their appointment.` |
| `$.transcription[?@.language == 'fr-FR'].summary` | `L'appelant a demandé à déplacer son rendez-vous.` |
| `$.transcription[?@.language == 'en-GB'].summary` | `The caller asked to move their appointment.` |

Save the last one as `summary_en_gb`, and a test on `summary_en_gb` measures the English
summary on every row, whichever position it appears in.

## Path syntax

Paths follow [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) and are at most 512
characters long. The parts you are most likely to need:

| Syntax | Selects |
| - | - |
| `$.a.b` | Key `b` inside key `a` |
| `$['x-request-id']` | A key that contains hyphens, spaces, or other punctuation |
| `$.items[0]`, `$.items[-1]` | The first or last array element |
| `$.items[*].name` | `name` in every element of `items` |
| `$.items[?@.score > 0.5]` | Elements of `items` that pass a filter expression |
| `$..summary` | Every `summary` key, at any depth |

The path is relative to the source column, so it never repeats the column's name.

## Check the match rate before saving

Select **Check path** in the form's **Preview** section to resolve the path against recent
rows from up to five of the project's data sources: the newest development commit and the
pipelines that most recently received data. The preview reports one total,
**Matched X of Y recent rows**, names any source where the path matches no row, and shows
a few distinct sample values. That way you catch a path that matches only some row shapes,
only some pipelines, or the wrong key.

> [!WARNING]
>
> The preview never blocks saving. A path that matches none of the recent rows
> is still saved, because a pipeline may not carry the field yet. Tests on the
> column are skipped wherever the path matches nothing, so read the preview's
> warning before you save.

On rows where the path matches nothing, the custom column is empty (null).

## When a path matches several values

A path with a wildcard, a filter, or `..` can match several values on one row, but a
column holds one value per row. The column's **reduction** picks it:

| Reduction | Value the row holds |
| - | - |
| `first` | The first match. This is the default. |
| `last` | The last match. |
| `longest` | The match with the most characters. |
| `shortest` | The match with the fewest characters. |
| `join` | All matches, concatenated with newlines. |

Non-string matches are compared and joined as their JSON text. The form saves new
columns with `first`; a column with any other reduction shows it as a tag in the list's
**Options** column.

## Stringified JSON

Some payloads hold JSON as a string, such as `{"transcript": "{\"summary\": \"...\"}"}`.
With **Parse stringified JSON** on (the default), the path still resolves through those
strings:

* The path runs against the raw value first. If it matches, that value is used, so a field
  whose JSON text you want to measure as text stays text.
* Only on a miss does Openlayer parse string values that hold a JSON object or array and
  retry, up to five levels deep. Strings such as `"42"` or `"true"` stay strings.

When a path resolves only this way, the preview says
**Resolves by parsing stringified JSON**, and the column shows an **unwraps JSON** tag in
the list.

## Use a custom column

* **Tests** — pick the custom column in a test's column picker. In a
  [`tests.json`](https://docs.openlayer.com/development/tests-json), in development or monitoring mode, use its name
  as the column value in `insightParameters`:

  ```json tests.json theme={null}
  "insightParameters": [{ "name": "column_name", "value": "summary_en_gb" }]
  ```

  When the path matches none of the rows a test evaluates, the test is skipped rather than
  failed, with a message that names the custom column and its path. A data source that
  doesn't carry the field yet doesn't break the test.

* **Filters** — you can filter tests and records on a custom column whose path is a chain of
  plain keys (such as `$.transcription.summary`) and that has **Parse stringified JSON**
  turned off. Filtering on any other custom column returns an error instead of empty
  results, and its filter offers no
  [value suggestions](https://docs.openlayer.com/tests/test-configuration.md#filter-value-suggestions).

* **Records** — custom columns appear in the records table and in the record detail, for
  every pipeline and development commit. A custom column can start out hidden in the
  table, for example when you saved a column layout before it existed. Enable it in the
  table's column options.

## Edit a custom column

You can change a custom column's source column, path, and **Parse stringified JSON**
setting. The name can't change, because tests refer to the column by name. The edit form
lists the tests that use the column.

An edit applies to every pipeline and every development test in the project from their
next evaluation. Existing test results don't change.

## Delete a custom column

You can't delete a custom column while any test uses it, archived tests included. The
list's **Used by** column shows which tests those are. Change those tests to stop using
the column, or delete them, and then delete the column.

Deleting an inference pipeline doesn't delete the project's custom columns.

## Limits

* [Exports](https://docs.openlayer.com/monitoring/export-data.md) do not include custom columns.
* Values keep the type they have in your data. A number stored as a string stays a string.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.