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.
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.
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.
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.
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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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.
Configuration checklist
Before publishing a View:
- Verify that Elasticsearch is enabled and the scoped content is indexed.
- Confirm content type, collection, location, and property field IDs against the project.
- Inspect representative property values before choosing visible facets.
- Validate the configuration and preview deterministic searches — see API & validation reference.
- 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.
- Open a copied deep link and verify repeated selections, display, and sort.
- Test every display and any custom renderer with missing fields and empty results.
- Test selection, each enabled action, and upload defaults with the permissions of the intended audience.
