Implementing Facets

A search interface often includes facets that allow the end user to narrow down a query to a specific subset of result items by selecting one or more dynamic filter values. This article provides guidelines for properly implementing facets on your own, assuming that you cannot use the Coveo JavaScript Search Framework in your custom search integration with the Coveo Cloud platform.

As a reference, you may want to look at the source code of the Facet component to see how it is implemented within the Coveo JavaScript Search Framework.

Standard Facet Actions

The following table lists some of the most frequent actions you may want to make available in a facet implementation.

Each entry indicates the specific Coveo Usage Analytics (Coveo UA) event category, actionCause/eventValue, eventType, and customData that must be logged when the action is performed, as well as a link to the corresponding implementation guidelines (see Logging Usage Analytics Events).

Facet action Usage analytics event actionCause eventType eventValue customData Implementation guidelines
Selecting a value Search facetSelect / documentField N/A N/A facetField, facetId, facetTitle, facetValue Toggling Facet Values
Un-selecting a value Search facetDeselect / documentField / breadcrumbFacet N/A N/A facetField, facetId, facetTitle, facetValue Toggling Facet Values
Excluding a value Search facetExclude N/A N/A facetField, facetId, facetTitle, facetValue Toggling Facet Values
Un-excluding a value Search facetUnexclude / breadcrumbFacet N/A N/A facetField, facetId, facetTitle, facetValue Toggling Facet Values
Resetting selected/excluded values Search facetClearAll / breadcrumbResetAll N/A N/A facetField, facetId, facetTitle Toggling Facet Values
Requesting more values N/A N/A N/A N/A N/A Requesting Additional Facet Values
Sorting values Custom N/A facet facetUpdateSort criteria, facetField, facetId, facetTitle Sorting Facet Values
Searching for a value Custom N/A facet facetSearch facetField, facetId, facetTitle Performing Facet Search

It is important to use the proper eventType, actionCause/eventValue, and customData when logging a usage analytics event for a specific type of action in a search interface. Otherwise:

Requesting and Rendering Facets

Conceptually, a facet relies on an existing field (e.g., @bookauthor) to render a sorted list of facet values (e.g., Poe, Edgar Allan, Clarke, Arthur C., Gibbon, Edward), each of which is accompanied by an estimated number of occurrences in the current query result set.

You can request the necessary data to render one or more facets through the groupBy search request parameter. Each Group By operation you include in a query populates a distinct object in the groupByResults array of that query response body.

Most of the time, when a query is sent from a faceted search interface, you will want to request and render/update facets. To do so:

  1. In the query, populate the groupBy search request parameter with a distinct Group By operation for each facet to render.

    Only the field Group By parameter is required. By default, when a Group By operation merely contains the field parameter, this operation requests up to ten facet values sorted by descending score order (or alphabetically in the case of ranged facets). You can use the maximumNumberOfValues and sortCriteria Group By parameters to override those settings (see Requesting Additional Facet Values and Sorting Facet Values).

    If the specified field references a field that does not exist in the index, or whose facet option is set to false, the corresponding groupByResults object in the query response body will contain an empty values array.

    Likewise, the values array will be empty if no value matching the query is found for the referenced field.

    In any case, a groupByResults object with an empty values array typically means that the corresponding facet should not be rendered in the search interface.

  2. When the query is executed and the Search API returns, for each facet to render in the search interface:

    1. In the query response body, retrieve the groupByResults object whose field value matches the field on which the facet relies.

    2. Use the lookupvalue (or value) and numberOfResults attributes of each object in the values array of the groupByResults object retrieved in step 2.a to render/update the facet.

      For a given object in the values array, the lookupValue and value attributes will contain identical data except when the object is a range value (see Requesting Ranged Facets).

  • In a groupByResults object, the field and globalComputedFieldResults properties, as well as all values object properties (lookupValue, numberOfResults, etc.) are duplicated in PascalCase for legacy reasons (e.g., each item in the values array of a groupByResults item has both the numberOfResults and NumberOfResults properties).

    Always use the camelCase version (e.g., numberOfResults).

    For the sake of brevity, examples in this article only include camelCase properties.

  • A Group By operation merely retrieves an array of groupByResults at query time; it has no effect whatsoever on the results array of the query response body (which contains the actual query result items). If you want to group query result items based on the value of a specific field, populate the filterField search request parameter as necessary before executing the query (see Handling Folded Results).

Executing a query which includes Group By operations to retrieve three facets (Book Author, Book Genre, and Book Format)

POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
 
Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  ...
  "groupBy": [
    ...
    {
      "field": "@bookauthor",
      "maximumNumberOfValues": 3
    },
    {
      "field": "@bookgenre"
    }
    {
      "field": "@bookformat"
    },
    ...
  ],
  "q": "fall",
  ...
}

200 OK response body (excerpt)

{
  ...
  "groupByResults": [
    ...
    {
      "field": "bookauthor",
      "globalComputedFieldResults": [],
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "Poe, Edgar Allan",
          "numberOfResults": 6,
          "score": 631739,
          "value": "Poe, Edgar Allan",
          "valueType": "Standard"
        }
        {
          "computedFieldResults": [],
          "lookupValue": "Clarke, Arthur C.",
          "numberOfResults": 11,
          "score": 399555,
          "value": "Clarke, Arthur C.",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Gibbon, Edward",
          "numberOfResults": 29,
          "score": 380154,
          "value": "Gibbon, Edward",
          "valueType": "Standard"
        }
      ]
    },
    {
      "globalComputedFieldResults": [],
      "field": "bookgenre",
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "Fantasy",
          "numberOfResults": 78,
          "score": 48927523,
          "value": "Fantasy",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Mystery",
          "numberOfResults": 89,
          "score": 8610185,
          "value": "Mystery",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Science-Fiction",
          "numberOfResult": 22,
          "score": 7478698,
          "value": "Science-Fiction",
          "valueType": "Standard"
        },
        ...
      ]
    },
    {
      "globalComputedFieldResults": [],
      "field": "bookformat",
      "values": [
        {
          "computedFieldResults": [],
          "score": 1752838,
          "value": "Paperback",
          "numberOfResults": 322,
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "score": 820350,
          "value": "Ebook",
          "numberOfResults": 261,
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "score": 70372,
          "value": "Hardcover",
          "numberOfResults": 178,
          "valueType": "Standard"
        }
      ]
    },
    ...
  ],
  ...
}

Requesting Ranged Facets

As a reference, you may want to look at the source code of the FacetRange component to see how it is implemented in the Coveo JavaScript Search Framework.

When a facet is based on a numeric or date field, its values must refer to ranges.

The easiest way to request ranged facet values is to set the generateAutomaticRanges Group By parameter to true when requesting the ranged facet. This will cause the index to automatically compute ranges based on the minimum and maximum values of the field referenced by the Group By operation.

This simple approach does not work with facets based on dynamic fields created by Query Functions. It also does not allow retrieved ranged facet values to have custom strings as their lookupValue. One work-around is to request an explicit set of ranges using the rangeValues Group By parameter. Each object in the rangeValues array must have a start (e.g., 0) and an end (e.g., 100). Optionally, it may also have a label (e.g., Short), which will be used as a custom lookupValue. By default, the end value is not included in the requested range, but you can set endInclusive to true to modify this behavior.

By default, ranged facet values are sorted alphabetically based on their lookupValue (rather than by descending score like regular facet values). This may result in awkward ordering.

In the values array of a groupByResults object corresponding to a ranged facet, a value whose lookupValue is 100..110 appears before one whose lookupValue is 20..30.

One way to work around this issue when using the rangeValues Group By parameter is to set the sortCriteria Group By parameter to nosort. This will return ranged facet values in the order in which they were originally specified.

Executing a query which includes Group By operations to retrieve two ranged facets (Book Price and Book Length)

POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
 
Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  ...
  "groupBy": [
    ...
    {
      "field": "@bookprice",
      "generateAutomaticRanges": true
    },
    {
      "field": "@booklength",
      "rangeValues": [
        {
          "start": 0,
          "end": 100,
          "label": "Short"
        },
        {
          "start": 100,
          "end": 500,
          "label": "Medium"
        },
        {
          "start": 500,
          "end": 10000,
          "label": "Long"
        }
      ],
      "sortCriteria": "nosort"
    },
    ...
  ],
  "q": "fall",
  ...
}

200 OK response body (excerpt)

{
  ...
  "groupByResults": [
    ...
    {
      "globalComputedFieldResults": [],
      "field": "bookprice",
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "100..110",
          "numberOfResults": 20,
          "score": 0,
          "value": "100.000000..110.000000",
          "valueType": "Standard"
        }
        {
          "computedFieldResults": [],
          "lookupValue": "10..20",
          "numberOfResults": 424,
          "score": 0,
          "value": "10.000000..20.000000",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "20..30",
          "numberOfResults": 217,
          "score": 0,
          "value": "20.000000..30.000000",
          "valueType": "Standard"
        },
        ...
      ]
    },
    {
      "globalComputedFieldResults": [],
      "field": "booklength",
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "Short",
          "numberOfResults": 138,
          "score": 161,
          "value": "0..99",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Medium",
          "numberOfResults": 970,
          "score": 432,
          "value": "100..499",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Long",
          "numberOfResults": 553,
          "score": 168,
          "value": "500..9999",
          "valueType": "Standard"
        }
      ]
    },
    ...
  ],
  ...
}

Requesting Slider Facets

As a reference, you may want to look at the source code of the FacetSlider component to see how it is implemented in the Coveo JavaScript Search Framework.

You may want to allow end users to specify their own custom range of values through a slider facet rather than selecting and/or excluding predefined values from a ranged facet.

One way to request the necessary data to render a slider facet is to perform a Group By operation in which maximumNumberOfValues is set to 1, generateAutomaticRanges is set to true, and advancedQueryOverride is set to an all-inclusive query syntax expression (e.g., @uri). This forces the index to compute a single range value starting with the lowest and ending with the highest value of the field referenced by the Group By operation.

When the end user interacts with the slider facet to specify a custom range, you can then alter the current advanced query expression (aq) accordingly (see Toggling Facet Values).

Executing a query which includes a Group By operation to retrieve the Book Price slider facet

POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
 
Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  ...
  "groupBy": [
    ...
    {
      "field": "@bookprice",
      "generateAutomaticRanges": true,
      "maximumNumberOfValues": 1,
      "advancedQueryOverride": "@uri"
    },
    ...
  ],
  "q": "fall",
  ...
}

200 OK response body (excerpt)

{
  ...
  "groupByResults": [
    ...
    {
      "globalComputedFieldResults": [],
      "field": "bookprice",
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "5..110",
          "numberOfResults": 1891,
          "score": 0,
          "value": "5.000000..110.000000",
          "valueType": "Standard"
        }
      ]
    },
    ...
  ],
  ...
}

Toggling Facet Values

In a typical faceted search interface, the end user can toggle (i.e., select/un-select or exclude/un-exclude) any number of facet values to alter the current advanced query expression (aq). When the end user interacts with a facet in such a way:

  1. Generate a query syntax expression that represents the updated facet state (see Coveo Cloud Query Syntax Reference).

    In a disjunctive facet based on the @bookauthor field, the currently selected values are Poe, Edgar Allan and Clarke, Arthur C. The end user interacts with the facet to exclude the Gibbon, Edward value. The updated facet state could be represented by a query syntax expression such as: (@bookauthor==("Poe, Edgar Allan", "Clarke, Arthur C.")) (NOT @author==("Gibbon, Edward"))

  2. Prepare a new query. Ensure that the aq search request parameter includes the expression generated in step 1.

  3. Call the Search API to execute the query prepared in step 2. When the Search API returns:

    1. Call the Usage Analytics Write API to log the corresponding Search event. In the request body:

      • Set the actionCause property to:

        • facetSelect if the end user has selected a single facet value.
        • facetUnselect if the end user has un-selected a single facet value.
        • facetExclude if the end user has excluded a single facet value.
        • facetUnexclude if the end user has un-excluded a single facet value.
        • facetClearAll if the end user has cleared all previously selected and/or excluded values.
        • facetRangeSlider or facetRangeGraph (whichever is more appropriate) if the end user has selected a custom range of numeric or date values through a slider facet.
      • Include the following key-value pairs in the customData property:

        • "facetField": <facetField>
        • "facetId": <facetId>
        • "facetTitle": <facetTitle> (do not include if actionCause is facetRangeSlider or facetRangeGraph)
        • "facetValue": <facetValue> (do not include if actionCause is facetClearAll, facetRangeSlider, or facetRangeGraph)
        • "facetRangeStart": <facetRangeStart> (only include if actionCause is facetRangeSlider or facetRangeGraph)
        • "facetRangeEnd": <facetRangeEnd> (only include if actionCause is facetRangeSlider or facetRangeGraph)

        where:

        • <facetField> (string) is the @-prefixed name of the field this facet is based on (e.g., @booklength).
        • <facetId> (string) is the unique identifier of the facet in which one or more values were toggled. Typically, this is the name of the field this facet is based on (e.g., @booklength).
        • <facetTitle> (string) is the display name of the facet in which one or more values were toggled. Typically, this is the human-readable name of the field this facet is based on (e.g., Book Length).
        • <facetValue> (string) is the name of the toggled facet value (e.g., Short).
        • <facetRangeStart> (string) is the first value of the end-user specified range.
        • <facetRangeEnd> (string) is the last value of the end-user specified range.

      The Coveo JavaScript Search Framework includes components that allow the end user to interact with facets indirectly (see the Breadcrumb and FieldValue components).

      If you implement similar widgets, be sure to use the appropriate actionCause and customData when logging a Search event after the end user has indirectly toggled one or more facet values:

      Action actionCause customData
      Select/un-select a single facet value by interacting with a query result documentField facetId, facetTitle, facetValue
      Un-select/un-exclude a single facet value by interacting with breadcrumbs breadcrumbFacet facetId, facetTitle, facetValue
      Clear all selected/excluded facet values by interacting with breadcrumbs breadcrumbResetAll N/A
    2. Update all facets in the search interface (see Requesting and Rendering Facets).

    3. Update the result list (see Rendering Query Results).

  1. Executing a query after the end user has excluded the Medium value from the Book Length facet by interacting with the facet directly

    POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "aq": "(NOT @booklength==(100..499))",
      "locale": "en-US",
      "groupBy": [
        ...
      ],
      "q": "fall",
      "searchHub": "BookstoreSearch",
      "tab": "All",
      ...
    }
    

    200 OK response body (excerpt)

    {
      ...
      "duration": 86,
      "groupByResults": [
        ...
      ],
      "results": [
        ...
      ],
      "searchUid": "a1d15d1b-5802-47ac-959f-35a0f2ca72de",
      ...
    }
    
  2. Executing a query after the end user has selected the Hardcover value from the Book Format facet by interacting with a query result

    POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
     
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "aq": "(NOT @booklength==(100..499)) (@bookformat==(\"Hardcover\"))",
      "locale": "en-US",
      "groupBy": [
        ...
      ],
      "q": "fall",
      "searchHub": "BookstoreSearch",
      "tab": "All",
      ...
    }
    

    200 OK response body (excerpt)

    {
      ...
      "duration": 75,
      "groupByResults": [
        ...
      ],
      "results": [
        ...
      ],
      "searchUid": "38e17ca1-c362-44a0-90bf-09669d908af3",
      ...
    }
    
  3. Executing a query after the end user has un-excluded the Medium value from the Book Length facet by interacting with the breadcrumbs

    POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "aq": "(@bookformat==(\"Hardcover\"))",
      "locale": "en-US",
      "groupBy": [
        ...
      ],
      "q": "fall",
      "searchHub": "BookstoreSearch",
      "tab": "All",
      ...
    }
    

    200 OK response body (excerpt)

    {
      ...
      "duration": 67,
      "groupByResults": [
        ...
      ],
      "results": [
        ...
      ],
      "searchUid": "7b83db54-ce56-4837-8c76-9f63b39b54a6",
      ...
    }
    
  4. Logging a batch of Search events for the three previous actions

    POST https://platform.cloud.coveo.com/rest/ua/v15/analytics/searches?visitor=28s6g49d-f81s-1435-2r5x153dle72 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    [
      {
        ...
        "actionCause": "facetExclude",
        "advancedQuery": "(NOT @booklength==(100..499))",
        "customData": {
          ...
          "facetField": "@booklength",
          "facetId": "@booklength",
          "facetTitle": "Book Length",
          "facetValue": "Medium",
          ...
        },
        "language": "en",
        "originLevel1": "BookstoreSearch",
        "originLevel2": "All",
        "queryText": "fall",
        "responseTime": 86,
        "searchQueryUid": "a1d15d1b-5802-47ac-959f-35a0f2ca72de",
        ...
      },
      {
        ...
        "actionCause": "documentField",
        "advancedQuery": "(NOT @booklength==(100..499)) (@bookformat==(\"Hardcover\"))",
        "customData": {
          ...
          "facetField": "@bookformat",
          "facetId": "@bookformat",
          "facetTitle": "Book Format",
          "facetValue": "Hardcover",
          ...
        },
        "language": "en",
        "originLevel1": "BookstoreSearch",
        "originLevel2": "All",
        "queryText": "fall",
        "responseTime": 75,
        "searchQueryUid": "38e17ca1-c362-44a0-90bf-09669d908af3",
        ...
      },
      {
        ...
        "actionCause": "breadcrumbFacet",
        "advancedQuery": "(@bookformat==(\"Hardcover\"))",
        "customData": {
          ...
          "facetField": "@booklength",
          "facetId": "@booklength",
          "facetTitle": "Book Length",
          "facetValue": "Medium",
          ...
        },
        "language": "en",
        "originLevel1": "BookstoreSearch",
        "originLevel2": "All",
        "queryText": "fall",
        "responseTime": 67,
        "searchQueryUid": "7b83db54-ce56-4837-8c76-9f63b39b54a6",
        ...
      }
    ]
    

Requesting Additional Facet Values

The maximumNumberOfValues Group By parameter allows you to specify up to how many facet values the index may retrieve to populate the values array of a given groupByResults object (the default value is 10). When completeFacetWithStandardValues is set to true and the cardinality of allowedValues is lower than the maximumNumberOfValues, the index will attempt to retrieve standard values until the values array contains maximumNumberOfValues.

You may also want to allow the end user to autonomously request more values to be displayed in a facet. When the end user interacts with a facet in such a way:

  1. Prepare a new query. Ensure that the numberOfResults search request parameter is set to 0. In the groupBy search request parameter, include a Group By operation to update the facet for which the end user has requested additional values. In this Group By operation:

    1. Set field to the name of the field that the facet is based on (e.g., @bookauthor).
    2. In the allowedValues array, include all of the values currently displayed by the facet (e.g., Poe, Edgar Allan, Clarke, Arthur C., Gibbon, Edward).
    3. Set maximumNumberOfValues to a higher value than the one that was specified the last time the facet was requested (e.g., 6).
    4. Set completeFacetWithStandardValues to true.
    5. Set all other Group By parameters to the value they were the last time the facet was requested.

    Although the query prepared in step 1 is not meant to return actual result items, be sure to include other search request parameters as usual, so that the query represents the current state of the search interface (aside from numberOfResults being forcefully set to 0 and groupBy not necessarily containing all of the operations required to update facets). Otherwise, the retrieved groupByResults object may not contain accurate or consistent facet values/occurrence counts.

  2. Call the Search API to execute the query prepared in step 1. When the Search API returns, re-render the updated facet as appropriate (see Requesting and Rendering Facets - Steps 2.a and 2.b).

When the end-user requests additional facet values to be displayed:

  • No specific usage analytics event needs to be logged.
  • The result list should not be updated.

Executing a query after the end user has requested additional values to be displayed in the Book Author facet

POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
 
Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  ...
  "aq": "(@bookformat==(\"Hardcover\"))",
  "groupBy": [
    {
      "allowedValues": [
        "Poe, Edgar Allan",
        "Clarke, Arthur C.",
        "Gibbon, Edward"
      ],
      "completeFacetsWithStandardValues": true,
      "field": "@bookauthor",
      "maximumNumberOfValues": 6
    }
  ],
  "locale": "en-US",
  "numberOfResults": 0,
  "q": "fall",
  "searchHub": "BookstoreSearch",
  "tab": "All",
  ...
}

200 OK response body (excerpt)

{
  ...
  "groupByResults": [
    {
      "field": "bookauthor",
      "globalComputedFieldResults": [],
      "values": [
        {
          "computedFieldResults": [],
          "lookupValue": "Poe, Edgar Allan",
          "numberOfResults": 1,
          "score": 631739,
          "value": "Poe, Edgar Allan",
          "valueType": "Standard"
        }
        {
          "computedFieldResults": [],
          "lookupValue": "Clarke, Arthur C.",
          "numberOfResults": 3,
          "score": 399555,
          "value": "Clarke, Arthur C.",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Gibbon, Edward",
          "numberOfResults": 7,
          "score": 380154,
          "value": "Gibbon, Edward",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Beckett, Samuel",
          "numberOfResults": 1,
          "score": 271647,
          "value": "Beckett, Samuel",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Delany, Samuel R.",
          "numberOfResults": 2,
          "score": 240684,
          "value": "Delany, Samuel R.",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [],
          "lookupValue": "Camus, Albert",
          "numberOfResults": 1,
          "score": 165923,
          "value": "Camus, Albert",
          "valueType": "Standard"
        }
      ]
    }
  ],
  "results": [],
  ...
}

Sorting Facet Values

The sortCriteria Group By parameter allows you to specify in what order the index should return the values of a groupByResults object (the default value being Score for regular values, or AlphaAscending for range values).

You may also want to allow the end user to autonomously change facet sort criteria. When the end user interacts with a facet in such a way:

  1. Prepare a new query. Ensure that the numberOfResults search request parameter is set to 0. In the groupBy search request parameter, include a Group By operation to update the facet for which the end user has selected a new sort criteria. In this Group By operation:

    1. Set field to the name of the field that the facet is based on (e.g., @bookformat).
    2. Set sortCriteria to the value that corresponds to the end user’s selection.
    3. Set all other Group By parameters to the value they were the last time the facet was requested.

    Although the query prepared in step 1 is not meant to return actual result items, be sure to include other search request parameters as usual, so that the query represents the current state of the search interface (aside from numberOfResults being forcefully set to 0 and groupBy not necessarily containing all of the operations required to update facets). Otherwise, the retrieved groupByResults object may not contain accurate or consistent facet values/occurrence counts.

  2. Call the Search API to execute the query prepared in step 1. When the Search API returns:

    1. Call the Usage Analytics Write API to log the corresponding Custom event. In the request body:

      • Set the eventType property to facet.
      • Set the eventValue property to facetUpdateSort.
      • Include the following key-value pairs in the customData property:

        • "criteria": <criteria>
        • "facetField": <facetField>
        • "facetId": <facetId>
        • "facetTitle": <facetTitle>

        where:

        • <criteria> (string) is the new selected sort criteria (e.g., AlphaAscending).
        • <facetField> (string) is the @-prefixed name of the field this facet is based on (e.g., @bookformat).
        • <facetId> (string) is the unique identifier of the facet in which the sort criteria was changed. Typically, this is the name of the field this facet is based on (e.g., @bookformat).
        • <facetTitle> (string) is the display name of the facet in which the sort criteria was changed. Typically, this is the human-readable name of the field this facet is based on (e.g., Book Format).
      • Set other required/optional properties as appropriate (language, originLevel1, originLevel2, etc.).
    2. Re-render the facet as appropriate (see Requesting and Rendering Facets - Step 2.a).

  1. Executing a query after the end user has selected an alphabetical sort criteria for the Book Format facet

    POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "groupBy": [
        {
          "field": "@bookformat",
          "sortCriteria": "AlphaAscending"
        }
      ],
      "locale": "en-US",
      "numberOfResults": 0,
      "q": "fall",
      "searchHub": "BookstoreSearch",
      "tab": "All",
      ...
    }
    

    200 OK response body (excerpt)

    {
      ...
      "groupByResults": [
        {
          "globalComputedFieldResults": [],
          "field": "bookformat",
          "values": [
            {
              "computedFieldResults": [],
              "score": 0,
              "value": "Ebook",
              "numberOfResults": 261
            },
            {
              "computedFieldResults": [],
              "score": 0,
              "value": "Hardcover",
              "numberOfResults": 178
            },
            {
              "computedFieldResults": [],
              "score": 0,
              "value": "Paperback",
              "numberOfResults": 322
            }
          ]
        }
      ],
      "results": [],
      ...
    }
    
  2. Logging a Custom event for the previous action

    POST https://platform.cloud.coveo.com/rest/ua/v15/analytics/custom?visitor=28s6g49d-f81s-1435-2r5x153dle72 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "customData": {
        ...
        "criteria": "AlphaAscending",
        "facetField": "@bookformat",
        "facetId": "@bookformat",
        "facetTitle": "Book Format",
        ...
      },
      "eventType": "facet",
      "eventValue": "facetUpdateSort",
      "language": "en",
      "originLevel1": "BookstoreSearch",
      "originLevel2": "All",
      ...
    }
    

Using Custom Facet Sort Criteria

If you want to define a custom facet sort criteria based on an aggregate operation performed on the values of a numeric field (i.e., average, maximum, minimum, or sum), use the computedFields Group By parameter along with either the ComputedFieldAscending or ComputedFieldDescending sortCriteria value (see Computed Fields).

  • You can include many aggregate operations in the computedFields array of a Group By operation. In the query response body, the computedFieldResults array property of each values element in the corresponding groupByResults object sequentially stores the results of these operations. When using the ComputedFieldAscending or ComputedFieldDescending sort criteria for a given Group By operation, only the results of the first specified aggregate operation are taken into account (if two facet values have identical first computedFieldResults values, the AlphaDescending sort criteria is used as a fallback).

  • The groupByResults object itself has a globalComputedFieldResults property which sequentially stores the results of the aggregate operation applied to all corresponding computedFieldResults (i.e., the average of all averages, the sum of all sums, etc.).

Executing a query which includes a Group By operation to sort the Book Author facet values based on average book ratings

POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
 
Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  ...
  "groupBy": [
    ...
    {
      "field": "@bookauthor",
      "maximumNumberOfValues": 3,
      "computedFields": [
        {
          "field": "@bookrating",
          "operation": "average"
        }
      ],
      "sortCriteria": "ComputedFieldDescending"
    },
    ...
  ],
  ...
}

200 OK response body (excerpt)

{
  ...
  "groupByResults": [
    ...
    {
      "field": "bookauthor",
      "globalComputedFieldResults": [ 6.7389380531 ],
      "values": [
        {
          "computedFieldResults": [ 8.9146341463 ],
          "lookupValue": "Follett, Ken",
          "numberOfResults": 3,
          "score": 0,
          "value": "Follett, Ken",
          "valueType": "Standard"
        }
        {
          "computedFieldResults": [ 8.307142857 ],
          "lookupValue": "Simmons, Dan",
          "numberOfResults": 3,
          "score": 0,
          "value": "Simmons, Dan",
          "valueType": "Standard"
        },
        {
          "computedFieldResults": [ 8.058823529 ],
          "lookupValue": "Golding, William",
          "numberOfResults": 2,
          "score": 0,
          "value": "Golding, William",
          "valueType": "Standard"
        }
      ]
    },
    ...
  ],
  ...
}

You may want to allow the end user to look for specific facet values by typing into a dedicated search input within a facet. When the end user interacts with a facet in such a way:

  1. On each valid keystroke in the corresponding facet search input:

    1. Retrieve the current value from the facet search input (e.g., a).
    2. Prepare a new query. Ensure that the numberOfResults search request parameter is set to 0. In the groupBy search request parameter, include a Group By operation for the facet in which the end user is searching. In this Group By operation:

      1. Set field to the name of the field that the searched facet is based on (e.g., @bookgenre).
      2. In the allowedValues array, include the original and the *-prefixed and *-suffixed value retrieved in step 1.a (e.g., a and *a*).
      3. Set maximumNumberOfValues to the maximum number of matching values to retrieve through facet search (e.g., 4). The default value is 10.
      4. Set all other Group By parameters to the value they were the last time the facet was requested.

      Although the query prepared in step 1.b is not meant to return actual result items, be sure to include other search request parameters as usual, so that the query represents the current state of the search interface (aside from numberOfResults being forcefully set to 0 and groupBy not necessarily containing all of the operations required to update facets). Otherwise, the retrieved groupByResults object may not contain accurate or consistent facet values/occurrence counts.

    3. Call the Search API to execute the query prepared in step 1.b. When the Search API returns:

      1. Call the Usage Analytics Write API to log the corresponding Custom event. In the request body:

        • Set the eventType property to facet.
        • Set the eventValue property to facetSearch.
        • Include the following key-value pairs in the customData property:

          • "facetField": <facetField>
          • "facetId": <facetId>
          • "facetTitle": <facetTitle>

          where:

          • <facetField> (string) is the @-prefixed name of the field this facet is based on (e.g., @bookgenre).
          • <facetId> (string) is the unique identifier of the facet in which facet search was performed. Typically, this is the name of the field this facet is based on (e.g., @bookgenre).
          • <facetTitle> (string) is the display name of the facet in which facet search was performed. Typically, this is the human-readable name of the field this facet is based on (e.g., Book Genre).
        • Set other required/optional properties as appropriate (language, originLevel1, originLevel2, etc.).
      2. Render the retrieved facet values as appropriate (e.g., in a dedicated result list below the facet search input).

These guidelines suggest a facet search implementation which is based on that of the Coveo JavaScript Search Framework(i.e., wildcard pattern matching as the end user types). You may opt to use an entirely different approach for your own implementation (to consume less queries per month (QPM), for example).

  1. Executing a query after the end user has typed a in the dedicated search input of the Book Genre facet

    POST https://platform.cloud.coveo.com/rest/search/v2 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "groupBy": [
        {
          "allowedValues": [
            "a",
            "*a*"
          ],
          "field": "@bookgenre",
          "maximumNumberOfValues": 4
        }
      ],
      "locale": "en-US",
      "numberOfResults": 0,
      "q": "fall",
      "searchHub": "BookstoreSearch",
      "tab": "All",
      ...
    }
    

    200 OK response body (excerpt)

    {
      ...
      "groupByResults": [
        {
          "values": [
            {
              "computedFieldResults": [],
              "lookupValue": "Fantasy",
              "numberOfResults": 78,
              "score": 48927523,
              "value": "Fantasy",
              "valueType": "Standard"
            },
            {
              "computedFieldResults": [],
              "lookupValue": "Romance",
              "numberOfResults": 106,
              "score": 1125782,
              "value": "Romance",
              "valueType": "Standard"
            },
            {
              "computedFieldResults": [],
              "lookupValue": "Adventure",
              "numberOfResults": 47,
              "score": 1063015,
              "value": "Adventure",
              "valueType": "Standard"
            },
            {
              "computedFieldResults": [],
              "lookupValue": "Biography",
              "numberOfResults": 94,
              "score": 831924,
              "value": "Biography",
              "valueType": "Standard"
            }
          ]
        }
      ],
      "results": [],
      ...
    }
    
  2. Logging a Custom usage analytics event for the previous action

    POST https://platform.cloud.coveo.com/rest/ua/v15/analytics/custom?visitor=28s6g49d-f81s-1435-2r5x153dle72 HTTP/1.1
        
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************
    

    Payload (excerpt)

    {
      ...
      "customData": {
        ...
        "facetField": "@bookgenre",
        "facetId": "@bookgenre",
        "facetTitle": "Book Genre",
        ...
      },
      "eventType": "facet",
      "eventValue": "facetSearch",
      "language": "en",
      "originLevel1": "BookstoreSearch",
      "originLevel2": "All",
      ...
    }