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 01 · Indexing and search views

Search fields: mapping areas into the index

A search field is a named slot in the index that areas from any layout can fill. How to create one, map an area into it, choose a type the area will accept, and make pages, posts and products come back from one search with the same parts.

Who this is for. The content modeller or theme developer deciding what a search can match and what a result can show. Afterwards you can map an area into the index, pick a field type the area will accept, and give every record type the same summary in a result list.

What a search field is

A search field is a name and a type, created under Search › Fields (the code calls it a master index field). It is defined once for the whole account and shared by every site on it. On its own it holds nothing. It fills when a layout area is pointed at it: from then on, every record whose layout has that area writes the area's value into fields.<name> in its search document.

Because the field is shared, areas from any number of layouts can fill it. A page layout's intro, a blog post layout's excerpt and a product description layout's shortDescription can all fill one summary field, and that is what lets one search return pages, posts and products with the same parts.

Field types, and the areas each accepts

A field's type has to suit the editor of the area's input type. An area pointed at a field of another type is refused when the layout is saved, with the message You need to use "text" field type for this area (or "double", "boolean", "date").

Area editorField typeMatched by a query's wordsWhat is stored
text, textarea, richtext, email, code, math, select, multiselectTextYesThe text with HTML tags removed; a select stores its option key, a multiselect each chosen key
numberDoubleNoA number
switchBoolNotrue or false
date, datetimeDateNoA date, and for a datetime its time

Only Text fields are matched against a query and can be highlighted; Double, Bool and Date fields are for filtering and sorting. The admin also offers an Integer type, but no area editor maps to it, so no area can fill one. Image, file, record, records, component and component collection areas cannot be mapped at all. A record or records area needs no mapping to be useful: the ids it holds go into the document's relatedRecords on their own.

Mapping an area

  1. Create the field under Search › Fields, with a name and a type.
  2. Open the layout under Customization › Layouts, edit the area, switch on Index in search and choose the field under Index field. The list offers only the fields whose type suits the area's input type.
  3. Save the layout. Mapping an area, or changing the field it fills, queues a reindex of every site on the account, so existing records gain the field without being saved again (Reindexing).
  4. If the view lists its full text search fields, add the new field there with a weight, or its words are never matched. A view that lists no fields searches every field (Search views).

Two record types, one field

A site that shows a one-line summary under every result maps one plain-text area per layout into a Text field named summary, and the body text into a second Text field, content, which is used for matching and for snippets but never printed whole:

LayoutArea (editor)Search field
A page layoutintro (textarea)summary
The blog post layoutexcerpt (textarea)summary
The product description layoutshortDescription (textarea)summary
A text component, used in collections on all threetext (richtext)content

The result template then prints the same parts for every result, whatever its type:

Template code search/search.html
{foreach $response->getResultItems() as $item}
  <article class="result">
    <h3><a href="{$item->getText('fullPath')|escape}">{$item->getText('name')|escape}</a></h3>
    {$summary = $item->getText('fields.summary')}
    {if $summary}<p>{$summary|truncate:200|escape}</p>{/if}
  </article>
{/foreach}
{% for item in response.getResultItems() %}
  <article class="result">
    <h3><a href="{{ item.getText('fullPath')|e }}">{{ item.getText('name')|e }}</a></h3>
    {% set summary = item.getText('fields.summary') %}
    {% if summary %}<p>{{ summary|u.truncate(200, '...', false)|e }}</p>{% endif %}
  </article>
{% endfor %}

Map a plain-text area into the field you print. Text from a rich-text area is stored with its tags removed but with the character references the editor wrote, such as &amp; and &nbsp;, which then show on the page once escaped (Character references in indexed text).

How the mapping behaves:

  • Components are included. The indexer walks every component, component collection and global component in the record, at any depth, and fills fields from their mapped areas too. Mapping the text area of a shared text component once fills content on every page that uses it.
  • A field holds every value. When several mapped areas fill the same field, it holds all their values as a list. getText() returns them joined with a comma and a space; getTexts() returns the list.
  • Default content counts. An area the author left empty is indexed with its default content, if it has one. An area with no value adds nothing.
  • A global component's text is copied. Its text is written into the document of each page that uses it, at the time that page is indexed. Editing the global component does not update those pages (What leaves the index stale).

Naming a field

Name fields with lower-case letters, digits and hyphens: summary, shoe-size. A filter in the URL reaches a field by its name, and PHP turns every dot and space in a query-string parameter name into an underscore before the platform reads it. The platform turns every underscore back into a dot, so:

  • ?fields.shoe-size=42 arrives as fields_shoe-size, becomes fields.shoe-size again, and works.
  • ?fields.shoe_size=42 becomes fields.shoe.size, which does not exist, so the search returns the same results as without the filter.
  • A name with a space can never be reached from the URL. This documentation site has a field named testing body: a search with ?fields.testing%20body=… returns exactly what it returns without it.

A $_search call from a template passes its parameters as an array, so a name with a space works there, but an underscore is still turned into a dot.

Renaming or deleting a field queues a reindex of every site on the account. Changing its type does not: it flags the index as needing a Rebuild instead (Reindex or Rebuild). Update every view and template that names a renamed field.

Go deeper

Was this page helpful?

Your answer goes to the docs team.