Implement facets

This is for:

Developer

A search interface often includes facets that allow the 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 implementing facets on your own, assuming that you can’t use the Coveo JavaScript Search Framework or the Atomic or Headless libraries in your custom search integration with the Coveo Platform.

Note
  • As a reference, you may want to look at the source code of the DynamicFacet component to see how it’s implemented in the Coveo JavaScript Search Framework.

Standard facet actions

The following table lists some actions that you may want to make available in a facet implementation.

Every action belongs to a specific Coveo Usage Analytics (Coveo UA) event category, and each entry indicates the actionCause, eventType, eventValue, and customData values (if applicable) that must be logged when the action is performed. Each entry also includes a link to the corresponding implementation guidelines.

Facet action UA event actionCause eventType eventValue customData Implementation guidelines

Select a value

Search

facetSelect / documentField

N/A

N/A

facetField, facetId, facetTitle, facetValue

Toggle facet values

Un-select a value

Search

facetDeselect / documentField / breadcrumbFacet

N/A

N/A

facetField, facetId, facetTitle, facetValue

Toggle facet values

Reset selected

Search

facetClearAll / breadcrumbResetAll

N/A

N/A

facetField, facetId, facetTitle

Toggle facet values

Request more values

Custom

showMoreFacetResults

dynamicFacet

showMoreFacetResults

facetField, facetId, facetTitle

Request additional facet values

Request less values

Custom

showLessFacetResults

dynamicFacet

showLessFacetResults

facetField, facetId, facetTitle

Request additional facet values

Search for a value

N/A

N/A

N/A

N/A

N/A

Perform facet search

Note

It’s important to use the correct actionCause, eventType, eventValue, and customData values when logging a UA event for a specific type of action in a search interface. Otherwise:

  • UA reports may become incoherent in the underlying Coveo organization, especially if that organization powers both Coveo JavaScript Search Framework and custom search interfaces.

  • The Coveo ML service may not function properly.

Request and render facets

Conceptually, a facet relies on an existing field (for example, @bookauthor) to render a sorted list of facet values (for example, Ernest Hemingway, Mark Twain), 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 facets query parameter. Each valid object you include in the facets query parameter will populate a distinct object in the facets array of that query response body.

Most of the time, when a query is sent from a faceted search interface, you’ll want to request and render or update facets. To do so, prepare a new query. In the request body:

  1. In the facets query parameter, include a corresponding facet object for each facet to display in the search interface.

  2. In the facet object operation, set field to the name of the field that the facet is based on (for example, @bookauthor).

When the Search API returns, for each facet to render in the search interface:

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

  2. Use the value, numberOfResults, and state attributes of each object in the values array from the query response to render or update the facet.

Call the UA Write API to log the corresponding search event. In the request body, include an object containing the following key-value pairs in the facetState property for each non-idle facet value:

  • "facetType": <TYPE>

  • "field": <FIELDNAME>

  • "id": <ID>

  • "state": <STATE>

  • "value": <VALUE>

  • "displayValue": <DISPLAY_VALUE> (optional)

  • "facetPosition": <FACET_POSITION> (optional)

  • "title": <TITLE> (optional)

  • "valuePosition": <VALUE_POSITION> (optional)

where:

  • <TYPE> (string) is the facet data type (for example, specific).

  • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (for example, @booklength).

  • <ID> (string) is the unique identifier of the facet in which one or more values were toggled. Typically, this is the @-prefixed name of the field that this facet is based on (for example, @booklength).

  • <STATE> (string) is the current state of the facet value (either auto_selected or selected).

  • <VALUE> (string) is the name of the toggled facet value (for example, Hardcover).

  • <DISPLAY_VALUE> (string) is the display name of the facet value (for example, Hardcover).

  • <FACET_POSITION> (integer) is the 1-based position of the facet (for example, 1).

  • <TITLE> (string) is the facet title (for example, Format)

  • <VALUE_POSITION> (integer) is the 1-based position of the value in the facet (for example, 2).

Examples of requesting and rendering facets

  1. A customer accesses the Books4Fun search interface. Once loaded, the search interface retrieves and renders the Book Format and Author facets with the following request:

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

    Payload (excerpt)

    {
      ...
      "q": "Doctor Sleep",
      "cq": "NOT @source==\"Movies4Fun\"",
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "type": "specific",
          "currentValues": [],
          ...
        },
        {
          "field": "bookauthor",
          "facetId": "@bookauthor",
          "type": "specific",
          "currentValues": [],
          ...
        }
      ],
      ...
    }

    200 OK response body (excerpt)

    {
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Hardcover",
              "state": "idle",
              "numberOfResults": 83
            },
            {
              "value": "Softcover",
              "state": "idle",
              "numberOfResults": 47
            },
            ...
          ],
          "indexScore": 0.435
        },
        {
          "facetId": "@bookauthor",
          "field": "bookauthor",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Charles Dickens",
              "state": "idle",
              "numberOfResults": 24
            },
            {
              "value": "Ernest Hemingway",
              "state": "idle",
              "numberOfResults": 26
            },
            {
              "value": "Mark Twain",
              "state": "idle",
              "numberOfResults": 23
            },
            ...
          ],
          "indexScore": 0.435
        }
      ],
      ...
    }
  2. Logging a search event for this action:

    POST https://analytics.cloud.coveo.com/rest/ua/v15/analytics/searches?visitor=********-****-****-******** HTTP/1.1
    
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************

    Payload (excerpt)

    [
      {
        "actionCause": "interfaceLoad",
        ...
        "customData": {
          "facetField": "@bookformat",
          "facetId": "@bookformat",
          "facetTitle": "Format",
        },
        ...
        "facetState": []
      }
    ]

Toggle facet values

In a typical faceted search interface, the user can toggle (that is, select or un-select) any number of facet values to alter the facets query parameter. When the user interacts with a facet in this way, prepare a new query. For each facet currently displayed in the search interface, include a corresponding object in the facets query parameter:

  1. Set facetId to the unique identifier of the facet.

  2. Set field to the name of the field the facet is based on.

  3. Set type to specific.

  4. If the facet is the one that was just interacted with, set freezeCurrentValues to true.

  5. If more than the originally specified numberOfResults are being requested for the facet, set isFieldExpanded to true (see Request additional facet values).

  6. For each value currently displayed in the facet, include a corresponding object in the currentValues array:

    1. Set the value property to the value of the current facet.

    2. Set the state property to idle if the facet isn’t selected, or set it to selected otherwise.

    3. Set preventAutoSelect property to false to ensure that Coveo ML automatically selects the facet value.

  7. Call the Search API to execute this query.

When the Search API returns, call the UA Write API to log the corresponding search event. In the request body:

  1. Set the actionCause property to:

    • facetSelect if the user has selected a single facet value.

    • facetUnselect if the user has un-selected a single facet value.

    • facetClearAll if the user has cleared all previously selected values.

  2. Include the following key-value pairs in the customData property:

    • "facetField": <FIELDNAME>

    • "facetId": <ID>

    • "facetTitle": <TITLE>

    • "facetValue": <VALUE>

    where:

    • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (for example, @booklength).

    • <ID> (string) is the unique identifier of the facet in which one or more values were toggled. Typically, this is the @-prefixed name of the field that this facet is based on (for example, @booklength).

    • <TITLE> (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 that this facet is based on (for example, Book Length).

    • <VALUE> (string) is the name of the toggled facet value (for example, Short).

    Note

    You have to use the appropriate actionCause and customData values if you’re interacting with a facet indirectly in your own custom component.

  3. For each non-idle facet, include an object containing the following key-value pairs in the facetState property:

    • "facetType": <TYPE>

    • "field": <FIELDNAME>

    • "id": <ID>

    • "state": <STATE>

    • "value": <VALUE>

    • "displayValue": <DISPLAY_VALUE>(optional)

    • "facetPosition": <FACET_POSITION> (optional)

    • "title": <TITLE>(optional)

    • "valuePosition": <VALUE_POSITION> (optional)

    where:

    • <TYPE> (string) is the facet data type (for example, specific).

    • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (for example, @booklength).

    • <ID> (string) is the unique identifier of the facet in which one or more values were toggled. Typically, this is the @-prefixed name of the field that this facet is based on (for example, @booklength).

    • <STATE> (string) is the current state of the facet value (either auto_selected or selected).

    • <VALUE> (string) is the name of the toggled facet value (for example, Hardcover).

    • <DISPLAY_VALUE> (string) is the display name of the facet value (for example, Hardcover).

    • <FACET_POSITION> (integer) is the 1-based position of the facet (for example, 1).

    • <TITLE> (string) is the facet title (for example, Format)

    • <VALUE_POSITION> (integer) is the 1-based position of the value in the facet (for example, 2).

  4. Set other required or optional properties as needed, such as language, originLevel1, or originLevel2.

When the UA Write API returns successfully:

Interact with a facet indirectly

The Coveo JavaScript Search Framework includes components that allow users to interact with facets indirectly, such as the Breadcrumb and FieldValue components.

If you implement your own components, be sure to use the appropriate actionCause and customData values when logging a search event after the user has indirectly toggled one or more facet values:

Action actionCause customData

Select or un-select a single facet value by interacting with a query result

documentField

facetId, facetTitle, facetValue

Un-select a single facet value by interacting with breadcrumbs

breadcrumbFacet

facetId, facetTitle, facetValue

Clear all selected facet values by interacting with breadcrumbs

breadcrumbResetAll

N/A

Examples of toggling facet values

  1. A customer is browsing products on the Books4Fun site. They select the Hardcover value from the Book Format facet, which executes the following query:

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

    Payload (excerpt)

    {
      ...
      "q": "Doctor Sleep",
      "cq": "NOT @source==\"Movies4Fun\"",
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "type": "specific",
          "currentValues": [
            {
              "value": "Hardcover",
              "state": "selected",
              "preventAutoSelect": false
            }
            {
              "value": "Softcover",
              "state": "idle",
              "preventAutoSelect": false
            },
            ...
          ],
          "numberOfValues": 3,
          ...
        },
        {
          "field": "bookauthor",
          "facetId": "@bookauthor",
          "type": "specific",
          "currentValues": [
            {
              "value": "Ernest Hemingway",
              "state": "idle",
              "preventAutoSelect": false
            },
            {
              "value": "Mark Twain",
              "state": "idle",
              "preventAutoSelect": false
            }
          ],
          "numberOfValues": 3,
          ...
        }
      ],
      ...
      "language": "en",
      "originLevel1": "Books4FunSearch",
      "originLevel2": "All",
      ...
    }

    200 OK response body (excerpt)

    {
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Hardcover",
              "state": "selected",
              "numberOfResults": 83
            },
            {
              "value": "Softcover",
              "state": "idle",
              "numberOfResults": 47
            },
            ...
          ],
          "indexScore": 0.435
        },
        {
          "facetId": "@bookauthor",
          "field": "bookauthor",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Charles Dickens",
              "state": "idle",
              "numberOfResults": 24
            },
            {
              "value": "Ernest Hemingway",
              "state": "idle",
              "numberOfResults": 26
            },
            {
              "value": "Mark Twain",
              "state": "idle",
              "numberOfResults": 23
            },
            ...
          ],
          "indexScore": 0.435
        }
      ],
      ...
    }
  2. Logging a search event for this action:

    POST https://analytics.cloud.coveo.com/rest/ua/v15/analytics/searches?visitor=********-****-****-******** HTTP/1.1
    
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************

    Payload (excerpt)

    [
      {
        "actionCause": "facetSelect",
        "eventType": "dynamicFacet",
        ...
        "customData": {
          "facetField": "@bookformat",
          "facetId": "@bookformat",
          "facetTitle": "Format",
          "facetValue": "Hardcover"
        },
        ...
        "facetState": [
          {
            "field": "@bookformat",
            "id": "@bookformat",
            "title": "Format",
            "facetType": "specific",
            "facetPosition": 1,
            "value": "Hardcover",
            "valuePosition": 2,
            "displayValue": "Hardcover",
            "state": "selected"
          }
        ]
      }
    ]

Request additional facet values

The numberOfValues facet request parameter lets you specify the maximum number of facet values that the index may retrieve to populate the values array of a given facet object (the default value is 8).

You may also want to allow the user to autonomously request to display more values in a facet. When the user interacts with a facet in this way, prepare a new query:

  1. Set the numberOfResults query parameter to 0.

  2. In the facets query parameter, include the facet object for which the user has requested additional values.

  3. In the facet object operation, set field to the name of the field that the facet is based on (for example, @bookauthor).

  4. For each value currently displayed in the facet, include a corresponding object in the currentValues array:

    1. Set the value property to the value of the current facet.

    2. Set the state property to idle if the facet isn’t selected, or set it to selected otherwise.

    3. Set preventAutoSelect property to false to ensure that Coveo ML automatically selects the facet value.

  5. Set numberOfValues to a higher value than the one that was specified the last time the facet was requested (for example, 16).

  6. Set isFieldExpanded to true.

  7. Set all of the other facet request parameters to the same values as the last time the facet was requested.

    Note

    Although this query isn’t meant to return actual result items, you should include the other search request parameters as usual. This way, it will represent the current state of the search interface, aside from numberOfResults being forcefully set to 0 and facets not necessarily containing all of the operations required to update facets. Otherwise, the retrieved facets object may not contain accurate or consistent facet values or occurrence counts.

  8. Call the Search API to execute this query.

When the Search API returns, call the UA Write API to log the corresponding custom event. In the request body:

  1. Set the actionCause property to showMoreFacetResults when showing more results or showLessFacetResults when showing less.

  2. Set the eventType property to dynamicFacet.

  3. Set the eventValue property to showMoreFacetResults when showing more results or showLessFacetResults when showing less.

  4. Include the following key-value pairs in the customData property:

    • "facetField": <FIELDNAME>

    • "facetId": <ID>

    • "facetTitle": <TITLE>

    where:

    • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (for example, @bookformat).

    • <ID> (string) is the unique identifier of the facet in which the sort criteria was changed. Typically, this is the name of the field that this facet is based on (for example, @bookformat).

    • <TITLE> (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 that this facet is based on (for example, Book Format).

  5. Set other required or optional properties as needed, such as language, originLevel1, or originLevel2.

When the UA Write API returns successfully, re-render the facet as appropriate.

Examples of requesting additional facet values

  1. A customer is browsing products on the Books4Fun site. They can’t find their favourite author among the currently rendered values in the Author facet, so they request to display more values. This interaction executes the following request:

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

    Payload (excerpt)

    {
      ...
      "q": "Doctor Sleep",
      "cq": "NOT @source==\"Movies4Fun\"",
      "numberOfResults": 0,
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "type": "specific",
          "currentValues": [
            {
              "value": "Hardcover",
              "state": "selected",
              "preventAutoSelect": false
            }
            {
              "value": "Softcover",
              "state": "idle",
              "preventAutoSelect": false
            },
            ...
          ],
          "numberOfValues": 3,
          ...
        },
        {
          "field": "bookauthor",
          "facetId": "@bookauthor",
          "type": "specific",
          "currentValues": [
            {
              "value": "Ernest Hemingway",
              "state": "idle",
              "preventAutoSelect": false
            },
            {
              "value": "Mark Twain",
              "state": "idle",
              "preventAutoSelect": false
            }
          ],
          "numberOfValues": 6,
          "isFieldExpanded": true,
          ...
        }
      ],
      ...
      "language": "en",
      "originLevel1": "Books4FunSearch",
      "originLevel2": "All",
      ...
    }

    200 OK response body (excerpt)

    {
      ...
      "facets": [
        {
          "facetId": "@bookformat",
          "field": "bookformat",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Hardcover",
              "state": "selected",
              "numberOfResults": 83
            },
            {
              "value": "Softcover",
              "state": "idle",
              "numberOfResults": 47
            },
            ...
          ],
          "indexScore": 0.435
        },
        {
          "facetId": "@bookauthor",
          "field": "bookauthor",
          "moreValuesAvailable": true,
          "values": [
            {
              "value": "Charles Dickens",
              "state": "idle",
              "numberOfResults": 24
            },
            {
              "value": "Ernest Hemingway",
              "state": "idle",
              "numberOfResults": 26
            },
            {
              "value": "Mark Twain",
              "state": "idle",
              "numberOfResults": 23
            },
            {
              "value": "Stephen King",
              "state": "idle",
              "numberOfResults": 14
            },
            ...
          ],
          "indexScore": 0.435
        }
      ],
      ...
    }
  2. Logging a custom event for this action:

    POST https://analytics.cloud.coveo.com/rest/ua/v15/analytics/custom?visitor=********-****-****-******** HTTP/1.1
    
    Accept: application/json
    Content-Type: application/json
    Authorization: Bearer **********-****-****-****-************

    Payload (excerpt)

    [
      {
        "actionCause": "showMoreFacetResults",
        "eventType": "dynamicFacet",
        "eventValue": "showMoreFacetResults",
        ...
        "customData": {
          "facetField": "@bookformat",
          "facetId": "@bookformat",
          "facetTitle": "Format",
        },
        ...
        "facetState": [
          {
            "field": "@bookformat",
            "id": "@bookformat",
            "title": "Format",
            "facetType": "specific",
            "facetPosition": 1,
            "value": "Hardcover",
            "valuePosition": 2,
            "displayValue": "Hardcover",
            "state": "selected"
          }
        ]
      }
    ]
Note

The following guidelines suggest a facet search implementation which is similar to that of the Coveo JavaScript Search Framework (that is, wildcard pattern matching as the user types). You may opt to use an entirely different approach for your own implementation.

You may want to allow the user to look for specific facet values by typing into a dedicated search input within a facet. When the user interacts with a facet in this way, on each valid keystroke:

  1. Retrieve the current value from the facet search input (for example, a).

  2. Prepare a new facet search request. In the request body:

    1. Set the field property to the name of the field that the facet being searched is based on (for example, bookauthor).

    2. Set the query property to the -prefixed and -suffixed value that was retrieved in step 1 (for example, a becomes a).

    3. Set the numberOfValues property to the maximum number of matching values to retrieve through facet search (for example, 4). The default value is 10.

    4. Set other optional properties as needed, such as ignoreValues or captions.

  3. Set all of the other facet request parameters to the same values as the last time the facet was requested.

    Note

    Although this query isn’t meant to return actual result items, you should include the other search request parameters as usual. This way, it will represent the current state of the search interface, aside from numberOfResults being forcefully set to 0 and facets not necessarily containing all of the operations required to update facets. Otherwise, the retrieved facets object may not contain accurate or consistent facet values or occurrence counts.

  4. Call the Search API to execute this query.

  5. When the Search API returns, render the retrieved facet values using the displayValue and count values of each object in the values array in the response body.

A customer is browsing products on the Books4Fun site. They can’t find their favourite author among the currently rendered values in the Author facet, so they begin typing the author’s name into the facet search box. This interaction executes the following request:

POST https://myorganizationid9sd8df7s.org.coveo.com/rest/search/v2/facet HTTP/1.1

Accept: application/json
Content-Type: application/json
Authorization: Bearer **********-****-****-****-************

Payload (excerpt)

{
  "field": "bookauthor",
  "numberOfValues": 4,
  "query": "*W*"
}

200 OK response body (excerpt)

{
  "values": [
    {
      "displayValue": "William Shakespeare",
      "rawValue": "William Shakespeare",
      "count": 10
    },
    {
      "displayValue": "William Faulkner",
      "rawValue": "William Faulkner",
      "count": 5
    },
    ...
  ],
  "moreValuesAvailable": false
}