Skip to content

How-to guides

Searching an index

This guide shows you how to construct search requests using query clauses, filters, facets, and pagination options. Use this guide when building a search interface for an index. For the complete query syntax and options, see the Search API.

Before you begin, ensure that you have:

  • A configured index with indexed documents. For more information, see Define an index.
  1. Send an initial search request to retrieve default results:

    POST /v1alpha1/indexes/products/search
    {}

    An empty request matches every document and returns the first 10 documents. Use this request to populate a search page before a user enters text or selects filters.

  2. Add a text search clause to match user search input:

    {
    "query": [
    { "type": "text", "text": "rain jack", "fields": { "name": 3, "description": null } }
    ]
    }

    The text clause searches text fields. Specify fields to define where to search and to set relative weights for matches in each field. If you omit fields, the query searches every searchable field. The search treats the last word as a prefix, so rain jack matches Rain jacket.

    Configure search options as needed:

    • Set "match": "user" to interpret quotes and minus signs as user search syntax. Use this setting for text entered by a user. Leave the default "match": "all" for text generated by your application code.
    • Set "join": "any" on a clause with "match": "user" to return documents that match one part of the typed text instead of all of it. Use this setting for a search box over documentation or articles. Excluded terms still apply.
    • Set "relax" to drop unmatched words instead of returning no results. By default, the search drops words that match no documents, and the response reports dropped words in relaxed.
    • When a number field of the index declares a unit, user mode reads a number typed next to the unit or next to a comparative word as a filter on that field, and the response reports it in interpreted. Set interpret to an object with fields to choose which fields the number is read on, such as the pricelist of the current customer. For more information, see Read numbers in the search box.

    If you query a field that is not enabled in the index definition, the API returns the error search:usage_unsupported.

  3. Separate the base search scope from user filter refinements:

    {
    "query": [
    { "type": "text", "text": "jacket" },
    { "field": "published", "match": { "value": true } }
    ],
    "filters": [
    { "field": "category", "match": { "type": "in", "values": ["Outerwear"] } },
    { "field": "price", "match": { "type": "range", "gte": 50, "lt": 200 } }
    ]
    }

    Define conditions in query and filters based on how they should affect facet counts:

    • Put base scope conditions in query (such as the search box, a catalog section, or a tenant identifier). query conditions narrow both search results and all facet counts.
    • Put user-selected refinements in filters (such as selected checkboxes). A facet excludes filters applied to its own field, which keeps other category values visible when a user selects a category like Outerwear.
  4. Request facet counts to build filter lists:

    {
    "facets": [
    { "field": "category", "limit": 20 },
    { "field": "price", "ranges": [
    { "to": 100 },
    { "from": 100, "to": 500 },
    { "from": 500 }
    ] }
    ]
    }

    Add a facets array to calculate value and range counts for fields marked with facet in the index definition:

    • Value facets return counts in values. Pass selected values back as an in filter on the same field.
    • Range facets return counts in buckets. Pass selected buckets back as a ranges matcher, using from as gte and to as lt.

    If you only need to refresh filter counts without retrieving hits, set "limit": 0. For counting category trees or bucketing dates, see facets.

    totalValues says how many distinct values match the search. The values past the limit get no count of their own, so a filter list that has to show one needs a higher limit.

    A facet returns at most 1000 values. To let a user type into a filter and reach the values past that cut, send the same query and filters with the typed text as prefix to POST /v1alpha1/indexes/{name}/facets/{field}/values. See Searching the values of a facet.

  5. Specify the sort order:

    { "sort": [ { "field": "price", "order": "asc" } ] }

    If you omit sort, the search returns results ordered by relevance score or ranking signals (such as top sellers in a browsed category).

    Specifying sort overrides relevance ranking. To allow users to return to relevance sorting, omit sort or pass [ { "type": "score" } ].

  6. Restrict response fields and add highlighting:

    {
    "fields": ["name", "price", "image"],
    "highlight": { "fields": { "name": {}, "description": { "fragments": 1 } } }
    }
    • Use fields to limit the returned fields. If omitted, the response includes all stored fields. To select a field inside an object, use dotted notation (for example, "fields": ["variants.price"]). If you request a field that the index cannot return, the request is rejected.
    • Use highlight to return matching text fragments wrapped in <em> tags (or custom tags set by pre and post). The returned fragments are not HTML-escaped; escape the text surrounding the highlight markers when rendering. Highlighting applies to the scoring query, not to filter matches.
  7. Set the search locale for localized fields:

    { "query": [ { "type": "text", "text": "regnjakke" } ], "locale": "nb" }

    The locale parameter selects and analyzes the appropriate language variant for the field. The search matches the tag as closely as the field’s declared locales allow (for example, nb-NO matches nb). If omitted, fields use their default locale. For supported language rules, see the locale reference.

  8. Paginate through search results:

    { "limit": 20, "after": "AW8..." }

    To paginate results, set limit and pass the cursor returned in after from the previous response. Cursor-based pagination with after has consistent performance at any depth. If you use offset, offset plus limit is capped by EXOFIND_SEARCH_MAX_PAGE_DEPTH. To request numbered pages, use pages. For more information, see Paginate search results.

  9. Count matches without fetching document hits:

    { "query": [ ... ], "limit": 0, "total": "exact" }

    If you need document counts without returning hits, set limit to 0. By default, total returns a lower bound count. Set "total": "exact" when you need an exact count of all matching documents, such as for a numbered pagination control.

Inspect the JSON response from the search endpoint to verify the results:

  • hits: Contains matching documents, each with id (the primary key) and document (the stored fields or requested fields).
  • facets: Contains facet counts under values or buckets.
  • relaxed: Contains any search terms that were dropped when relax was active.
  • interpreted: Contains any filters and remaining query text read from numbers typed with units or comparative words.
  • total: Contains the count of matching documents.

Exofind is built by Level Four AB and is available under the Apache License 2.0.