Guide 11 · Developer docs

Search, filtering and faceted navigation

Site search on Core dna is an Elasticsearch index that the platform fills and queries, and a page you build. What reaches the index, search views, the search page, product facets, filtered listings that need no index, and when to go beyond the built-in index.

L2
Unit 04 · Listings without the index

When a listing should become a search view

A plain listing is always current, needs no index and respects the visitor's permissions; a search view matches words, ranks across record types and counts facets. Where the line between them falls, and what each side costs, so that nothing moves onto the index without a reason.

Who this is for. The developer deciding whether a new filtered page needs a search view, and the reviewer asking why it has one. Afterwards you can name the requirement that forces a view, or show that a listing is the better answer.

The line

A listing filters one module's records on their own fields, in a fixed order or a chosen sort. Move to a search view when the page needs something a listing cannot do:

  • Free-text matching. A box where the visitor types words and expects every word form, typing errors and the best match first. A listing's contains filter, %25field=, is a plain substring match on one field: no stems, no fuzziness, no ranking, and on a large table it reads every row (Queries, ranking and highlighting).
  • One result set across types, pages, posts, products and FAQ answers together, in relevance order. A listing is one module's records.
  • Counts beside each option, for products. A listing can filter by tag or category but does not count what each option would leave; that is the facets index (Facets and the mapping template).

Below that line, a listing is the better answer, and a rule of thumb finds the line quickly: if the visitor picks from a known set of values, filter a listing; if the visitor types words, use a view.

What a listing gives you that a view does not

  • It is always current. It reads the records directly, so there is no indexing delay and no stale index to explain (Why a new record is not findable yet).
  • It respects the visitor's permissions. A signed-in member sees the records their groups may read. The index holds only what a signed-out visitor may read, so members-only content can only ever be a listing.
  • It covers every module. Custom entity records are never in site search; a custom entity's listing route, or a $_query lookup, is the only way to filter them.
  • Its count is exact, where a search count stops at 10,000.
  • There is no view to maintain: no mapping, no reindex after a model change, no scope to lock.

A members-only search is the clearest case: it cannot be a view at all. A $_query lookup applies the signed-in member's permissions and the published filter, and is enough for a short list of matching titles:

Template code pages/pages/members-search.html
{$_response->preventCaching()}
{$q = $_request->getParameter('q')}
{if is_string($q) && trim($q) !== ''}
  {$found = $_query->findBy('Pages\Entities\Page', ['%activeRevision.name' => trim($q)], ['created' => 'DESC'], 20)}
  <ul class="member-results">
    {foreach $found as $result}
      <li><a href="{$result->getFullPath()|escape}">{$result->getName()|escape}</a></li>
    {foreachelse}
      <li>No page title contains &ldquo;{$q|escape}&rdquo;.</li>
    {/foreach}
  </ul>
{/if}
{% do _response.preventCaching() %}
{% set q = _request.getParameter('q') %}
{% if q is not null and q is not iterable and q|trim != '' %}
  {% set found = _query.findBy('Pages\\Entities\\Page', {'%activeRevision.name': q|trim}, {'created': 'DESC'}, 20) %}
  <ul class="member-results">
    {% for result in found %}
      <li><a href="{{ result.getFullPath()|e }}">{{ result.getName()|e }}</a></li>
    {% else %}
      <li>No page title contains &ldquo;{{ q|e }}&rdquo;.</li>
    {% endfor %}
  </ul>
{% endif %}

It matches titles only, as a substring, which is the trade: no ranking and no stems, but nothing a member may read is left out and nothing they may not read is shown.

What a view costs

  • Mapping work: layout area text is invisible until each area is mapped to a search field (Search fields).
  • Delay and staleness: new records arrive in seconds or minutes, and some changes, a global component's text or a renamed tag, leave the index stale until a reindex (What leaves the index stale).
  • Configuration to guard: a new view lets visitors change its scope from the URL until the filters are locked (What a visitor can change).
  • Public content only, and a count that stops at 10,000.

Deciding

The page needsUse
Filtering by tag, category, author or a field, from known valuesA listing, through its routes or query parameters
Members-only records, or custom entity recordsA listing, or a $_query lookup
Words typed by the visitor, matched forgivingly and rankedA search view
Pages, posts and products in one listA search view on Site Search
A product finder with a count beside each optionA Faceted Search view
Go deeper

Was this page helpful?

Your answer goes to the docs team.