Vertesia Documentation

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.

FieldMeaning
type_idsRestrict to specific content types.
locationsRestrict to absolute content location subtrees.
collection_idsRestrict to members of specific collections (a hard membership boundary).
include_collection_descendantsExtend collection_ids to descendant collections.
fixed_filterAn author-provided Elasticsearch query subtree, validated against the supported allowlist.
head_onlySearch 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 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.

SourceUse it forKey configuration
termsBounded property values such as customer, state, or statusfield, size, sort
hierarchyOrdered property drill-down such as State → Citytwo or more levels, each with a field
rangeAuthored numeric or date bucketsfield and labeled ranges
collectionCurated or dynamic collection membershiproots, include_descendants
locationHierarchical content pathsabsolute 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.

DisplayBest for
listCompact universal results with title, subtitle, description, media, and badges
tableOperational inventories with typed columns and configured sorting
cardsRich summaries with media, fields, and badges
galleryMedia-first content
boardCards 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:

  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.
  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.

Was this page helpful?