---
title: "Configuration"
source: "https://docs.vertesiahq.com/content/view-experiences/configuration"
markdown: "https://docs.vertesiahq.com/llms/content/view-experiences/configuration.md"
---

# Configuration

A View Experience configuration has four sections — `scope`, `navigation`, `search`, and `results` — plus an optional
`layout`. This page covers scope, navigation, and results in depth; search behavior has its own page,
[Search & agentic planning](/content/view-experiences/search).

## A complete example

The following configuration creates a Sales Orders browser. It scopes to the Sales Order content type, exposes the
`sold_to_state`, `customer`, and `status` properties as count-bearing facets, offers deterministic search with
structured key terms, and provides table and card displays with sorting.

```typescript {{ title: 'sales-orders-view.ts' }}
import type { ViewExperienceConfiguration } from '@vertesia/common';

export const salesOrdersView = {
    name: 'Sales Orders',
    description: 'Browse and search sales orders by customer, destination state, and status.',
    enabled: true,
    layout: {
        mode: 'worklist',
        navigation_position: 'sidebar',
    },
    scope: {
        type_ids: ['<SALES_ORDER_TYPE_ID>'],
        head_only: true,
    },
    navigation: [
        {
            id: 'sold_to_state',
            label: 'Sold-to state',
            source: 'terms',
            field: 'properties.sold_to_state',
            presentation: 'chips',
            multi_select: true,
            size: 50,
            sort: 'label',
        },
        {
            id: 'customer',
            label: 'Customer',
            source: 'terms',
            field: 'properties.customer',
            presentation: 'list',
            multi_select: true,
            size: 50,
            sort: 'count',
        },
        {
            id: 'status',
            label: 'Status',
            source: 'terms',
            field: 'properties.status',
            presentation: 'chips',
            multi_select: true,
            size: 20,
            sort: 'count',
        },
    ],
    search: {
        mode: 'deterministic',
        placeholder: 'Search sales orders',
        fields: [
            { field: 'name', description: 'Sales order or source file name.', type: 'text', mode: 'full_text', boost: 2 },
            { field: 'text', description: 'Full ingested and OCR sales-order text.', type: 'text', mode: 'full_text' },
        ],
        key_terms: [
            { id: 'customer', label: 'Customer', field: 'properties.customer', type: 'keyword', multiple: true, operator: 'term' },
            { id: 'status', label: 'Status', field: 'properties.status', type: 'keyword', multiple: true, operator: 'term' },
        ],
    },
    results: {
        default_display: 'table',
        allow_display_switch: true,
        selection: {
            mode: 'multiple',
            select_all: 'page',
        },
        actions: {
            include_defaults: true,
        },
        drop: {
            handler: 'upload',
            params: {
                type_id: '<SALES_ORDER_TYPE_ID>',
                location: '/Sales Orders/Inbox',
                properties: { source: 'sales-order-view' },
                allow_folders: true,
            },
        },
        default_sort: 'recent',
        sort_options: [
            { id: 'recent', label: 'Recently updated', sort: [{ field: 'updated_at', order: 'desc' }] },
            { id: 'customer_asc', label: 'Customer', sort: [{ field: 'properties.customer', order: 'asc' }] },
        ],
        displays: [
            {
                id: 'table',
                label: 'Table',
                type: 'table',
                page_size: 25,
                columns: [
                    { field: 'name', label: 'Sales order' },
                    { field: 'properties.customer', label: 'Customer' },
                    { field: 'properties.sold_to_state', label: 'State' },
                    { field: 'properties.status', label: 'Status', format: 'badge' },
                    { field: 'updated_at', label: 'Updated', format: 'date' },
                ],
            },
            {
                id: 'cards',
                label: 'Cards',
                type: 'cards',
                page_size: 24,
                columns: 3,
                title: { field: 'name' },
                description: { field: 'description' },
                fields: [
                    { field: 'properties.customer', label: 'Customer' },
                    { field: 'properties.sold_to_state', label: 'State' },
                ],
                badges: [{ field: 'properties.status', format: 'badge' }],
            },
        ],
    },
} satisfies ViewExperienceConfiguration;
```

Replace `<SALES_ORDER_TYPE_ID>` with the ID of the Sales Order content type. Confirm indexed field names and values
before exposing a property as a facet or sort field. On current indexes, property strings are keyword fields, so use a
path such as `properties.customer` directly.

## Scope

`scope` fixes the content a View covers. It is applied on every execution and cannot be overridden by the caller.

| Field | Meaning |
| --- | --- |
| `type_ids` | Restrict to specific content types. |
| `locations` | Restrict to absolute content location subtrees. |
| `collection_ids` | Restrict to members of specific collections (a hard membership boundary). |
| `include_collection_descendants` | Extend `collection_ids` to descendant collections. |
| `fixed_filter` | An author-provided Elasticsearch query subtree, validated against the supported allowlist. |
| `head_only` | Search only head revisions (the default); set `false` to include historical revisions. |

Use `scope.collection_ids` or `scope.locations` when membership or location is a hard boundary for the whole View, and
a *navigation* item (below) when the user should choose a collection or subtree interactively. A `fixed_filter` that
uses an unsupported query type is rejected by create, update, preview, and execution validation — see
[Troubleshooting](/content/view-experiences/troubleshooting).

## Layout

`layout` controls the frame. `navigation_position` is `sidebar` (default) or `top`; `mode` is `browse` (default) or
`worklist`, which reorders the results-first layout for operational queues.

## Navigation

Navigation items share one result contract: each item returns the selected values and count-bearing nodes. The
`source` determines how those nodes are produced. All active navigation items intersect with each other and with
search input.

| Source | Use it for | Key configuration |
| --- | --- | --- |
| `terms` | Bounded property values such as customer, state, or status | `field`, `size`, `sort` |
| `hierarchy` | Ordered property drill-down such as State → City | two or more `levels`, each with a `field` |
| `range` | Authored numeric or date buckets | `field` and labeled `ranges` |
| `collection` | Curated or dynamic collection membership | `roots`, `include_descendants` |
| `location` | Hierarchical content paths | absolute `roots`, `depth` |

Navigation can be rendered as `tree`, `list`, `select`, or `chips`. Facets and collection/location navigation can be
single- or multi-select; a property hierarchy always represents one path.

### Property facets

A `terms` navigation item runs an aggregation on its configured field and returns values with result counts. Use
facets for normalized, bounded values such as `properties.customer`, `properties.status`, or `properties.sold_to_state`.
Avoid placing identifiers, free text, or mostly unique values in a visible terms facet.

Range facets must target a genuinely numeric or date-mapped field. Because JSON has no date scalar, date-looking
strings remain keyword fields unless the project explicitly maps the path as `date`, and any explicit mapping change
requires a full reindex first. See
[the typed-indexing prerequisite](/content/view-experiences/search#typed-property-indexing).

### Property hierarchies

A property hierarchy turns any ordered set of mapped fields into drill-down navigation. It does not require the
properties themselves to be nested:

```json
{
  "id": "geography",
  "label": "Geography",
  "source": "hierarchy",
  "presentation": "tree",
  "levels": [
    { "id": "state", "label": "State", "field": "properties.sold_to_state", "sort": "label" },
    { "id": "city", "label": "City", "field": "properties.sold_to_city", "sort": "label" }
  ]
}
```

The first execution returns State buckets. Selecting Florida applies its exact typed value and returns only City
buckets beneath Florida. The response includes `breadcrumbs` for the selected path; **treat returned node IDs as
opaque** and send them back unchanged in `navigation.geography`.

A property hierarchy always represents one path and cannot be multi-select. Add a separate `terms` facet when users
also need to combine several values at one level. Order levels from broadest to most specific, and choose bounded,
aggregatable fields. The same mechanism works for Brand → Product line → Model, Department → Team, or any meaningful
sequence.

### Collection browsing

Collection navigation follows collection identity rather than content location:

```json
{
  "id": "sales_order_collections",
  "label": "Collections",
  "source": "collection",
  "presentation": "tree",
  "roots": ["<COLLECTION_ID>"],
  "include_descendants": true
}
```

When no `roots` are supplied, the browser starts from visible top-level collections. Static collections resolve to
their member IDs; dynamic collections resolve through their stored query. With `include_descendants`, selecting a
collection also includes members of descendant collections. Resolution happens on the server, so collection visibility
and content security remain enforced. One selected collection returns a `breadcrumbs` path. If a level has more than
50 collections, the response is marked `truncated`; use `navigation_queries.<navigation-id>` to filter collection names
server-side and reach nodes outside the first page.

### Location hierarchy

Location navigation browses slash-delimited content paths:

```json
{
  "id": "location",
  "label": "Location",
  "source": "location",
  "presentation": "tree",
  "roots": ["/Sales Orders"],
  "depth": 6
}
```

Selecting `/Sales Orders/West` includes that path and its descendants. Roots must be absolute. Location browsing
returns immediate children in the current context and may report a truncation warning for a very high-cardinality
hierarchy. When exactly one location is selected, the response includes clickable `breadcrumbs` from the configured
root to the selected path.

## Result displays

A View can offer multiple declarative displays. Set `allow_display_switch` to let the user move between them without
losing the current query or navigation state.

| Display | Best for |
| --- | --- |
| `list` | Compact universal results with title, subtitle, description, media, and badges |
| `table` | Operational inventories with typed columns and configured sorting |
| `cards` | Rich summaries with media, fields, and badges |
| `gallery` | Media-first content |
| `board` | Cards grouped by a property such as `properties.status` |

Result fields use dot-separated paths such as `properties.customer`, `type.name`, or `updated_at`. Available formats
are `text`, `date`, `number`, `badge`, `user`, `content_type`, and `location`. Media can come from a content
thumbnail, a property, or the content type icon.

Only **authored** sort options can be selected by callers. A table column can reference one of those options through
`sort_option`; callers do not submit arbitrary Elasticsearch sort clauses.

## Selection and actions

Selection is part of the result experience, not the search request. It stays client-side and is cleared when the
query or navigation changes. Multiple selection can expose a page-level checkbox:

```json
{
  "selection": { "mode": "multiple", "select_all": "page" },
  "actions": {
    "include_defaults": true,
    "exclude_defaults": ["delete"],
    "items": [
      {
        "id": "approve",
        "label": "Approve",
        "handler": "approve-orders",
        "placement": "selection",
        "requires_selection": "any",
        "confirm": true,
        "params": { "status": "approved" }
      }
    ]
  }
}
```

When selection is enabled, `include_defaults` defaults to `true`. The built-in actions are export and, for users with
delete permission, delete. Use `exclude_defaults` to hide either action. An item in `actions.items` only declares its
label, placement, selection requirement, confirmation, and parameters; the embedding application must register code
for its `handler`. If the app does not provide that handler, the action is hidden. `export` and `delete` are reserved
handler and action IDs.

## Drop to upload

Persisted JSON can turn the result area into the standard content uploader:

```json
{
  "drop": {
    "handler": "upload",
    "accept": ["files"],
    "params": {
      "type_id": "<CONTENT_TYPE_ID>",
      "collection_id": "<COLLECTION_ID>",
      "location": "/Incoming/Sales Orders",
      "properties": { "source": "sales-order-view" },
      "allow_folders": true
    }
  }
}
```

All parameters are optional. They preselect the content type, collection, and location, apply default properties to
new objects, and control folder upload while retaining the normal upload review flow. The runtime checks content-write
permission before enabling this target.

Custom drop behavior is deliberately **code-only**. It cannot be named or selected in persisted JSON. Register it on
the embedded `ViewExperience`; the application then owns validation, authorization, side effects, and error handling.
See [Embedding in applications](/content/view-experiences/embedding#actions-and-drop-contributions).

## Configuration checklist

Before publishing a View:

1. Verify that Elasticsearch is enabled and the scoped content is indexed.
2. Confirm content type, collection, location, and property field IDs against the project.
3. Inspect representative property values before choosing visible facets.
4. Validate the configuration and preview deterministic searches — see [API & validation reference](/content/view-experiences/reference).
5. If agentic search is enabled, test successful planning, deterministic fallback, low-confidence behavior, and — when
   configured — reranking order, why-match annotations, and Elasticsearch-order fallback.
6. Open a copied deep link and verify repeated selections, display, and sort.
7. Test every display and any custom renderer with missing fields and empty results.
8. Test selection, each enabled action, and upload defaults with the permissions of the intended audience.