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

# Views

> Ship pre-configured saved views — column order, filters, groups — for objects in your app.

A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app — typically a default index view for each custom object you create.

```ts src/views/example-view.ts theme={null}
import { defineView, ViewKey } from 'twenty-sdk/define';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object';

export default defineView({
  universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  name: 'All example items',
  objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
  icon: 'IconList',
  key: ViewKey.INDEX,
  position: 0,
  fields: [
    {
      universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
      fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
      position: 0,
      isVisible: true,
      size: 200,
    },
  ],
});
```

## Key points

* `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object.
* `key` determines the view type — `ViewKey.INDEX` is the main list view for the object.
* `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`.
* You can also declare `filters`, `filterGroups`, `groups`, and `fieldGroups` for advanced configurations.
* `position` controls ordering when multiple views exist for the same object.

## Filters

A view can ship with pre-applied filters. Each filter has three coordinates: the **field** being filtered, the **operand** (how to compare), and the **value** (what to compare against). All three must line up — using an operand that doesn't apply to a field type will be rejected at sync time.

```ts theme={null}
import { ViewFilterOperand } from 'twenty-shared/types';

filters: [
  {
    universalIdentifier: '...',
    fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
    operand: ViewFilterOperand.IS,
    value: ['ACTIVE'],
  },
],
```

### Supported operands per field type

| Field type                                                                                         | Supported operands                                                                                                 |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                         |
| `ACTOR.source`, `ACTOR.workspaceMemberId`                                                          | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                                         |
| `SELECT`                                                                                           | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                                         |
| `MULTI_SELECT`                                                                                     | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                         |
| `RELATION`                                                                                         | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                                         |
| `NUMBER`                                                                                           | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY`                          |
| `RATING`                                                                                           | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY`                                    |
| `CURRENCY`, `CURRENCY.amountMicros`                                                                | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                          |
| `CURRENCY.currencyCode`                                                                            | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                                         |
| `DATE`, `DATE_TIME`                                                                                | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `BOOLEAN`                                                                                          | `IS`                                                                                                               |
| `UUID`                                                                                             | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY`                                                                         |
| `TS_VECTOR`                                                                                        | `VECTOR_SEARCH`                                                                                                    |

> Field types with similar names can use entirely different operands — `SELECT` and `MULTI_SELECT` being a common case.

### Value shape per operand

The `value` field is always a JSON-serializable value, but its expected shape depends on the operand:

| Operand family                                        | Value shape                    | Example                  |
| ----------------------------------------------------- | ------------------------------ | ------------------------ |
| `IS`, `IS_NOT` on `SELECT`                            | array of option keys (strings) | `['ACTIVE', 'PENDING']`  |
| `CONTAINS`, `DOES_NOT_CONTAIN` on `MULTI_SELECT`      | array of option keys (strings) | `['TAG_A']`              |
| `IS`, `IS_NOT` on `RELATION`                          | array of record IDs (uuids)    | `['c5a1...']`            |
| `CONTAINS`, `DOES_NOT_CONTAIN` on text-like fields    | string                         | `'acme'`                 |
| `IS`, `IS_NOT` on `NUMBER`                            | string (the value)             | `'5'`                    |
| `IS` on `RATING` / `UUID`                             | string (the value)             | `'5'`                    |
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`         | string (the bound)             | `'10'`                   |
| `IS`, `IS_BEFORE`, `IS_AFTER` on `DATE` / `DATE_TIME` | ISO 8601 string                | `'2025-01-01T00:00:00Z'` |
| `IS_EMPTY`, `IS_NOT_EMPTY`                            | empty string                   | `''`                     |
| `IS` on `BOOLEAN`                                     | `'true'` or `'false'`          | `'true'`                 |

## How views show up in the UI

A view by itself isn't reachable from the sidebar. To make it appear there, pair it with a [navigation menu item](/developers/extend/apps/layout/navigation-menu-items) of type `VIEW` that points at the view's `universalIdentifier`. That's the canonical pattern: every custom object typically ships a default view + a sidebar entry that opens it.
