Implement 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 can’t use the JavaScript Search Framework in your custom search integration with the Coveo Platform.

Standard Facet Actions

The following table lists some of the most frequent actions 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 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
Resetting selected Search facetClearAll / breadcrumbResetAll N/A N/A facetField, facetId, facetTitle Toggling Facet Values
Requesting more values Custom showMoreFacetResults dynamicFacet showMoreFacetResults facetField, facetId, facetTitle Requesting Additional Facet Values
Requesting less values Custom showLessFacetResults dynamicFacet showLessFacetResults facetField, facetId, facetTitle Requesting Additional Facet Values
Searching for a value N/A N/A N/A N/A N/A Performing Facet Search

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

  • Usage analytics reports may become incoherent in the underlying Coveo organization (especially if that organization powers both JavaScript Search Framework and custom search interfaces).
  • The Coveo ML service may not function properly.

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., 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 will want to request and render/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 (e.g., @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/update the facet.

Call the Usage Analytics 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>

where:

  • <TYPE> (string) is the facet data type (e.g., specific).

  • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (e.g., @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 (e.g., @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 (e.g., Hardcover).

Example

A customer is 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
    }
  ],
  ...
}

A Search event is logged for this action:

POST https://platform.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": []
  }
]

Toggling Facet Values

In a typical faceted search interface, the end user can toggle (i.e., select/un-select) any number of facet values to alter the facets query parameter. When the end 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 Requesting 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 Usage Analytics Write API to log the corresponding Search event. In the request body:

  1. 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.

    • facetClearAll if the end 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 (e.g., @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 (e.g., @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 (e.g., Book Length).

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

    The JavaScript Search Framework includes components that allow the end user to interact with facets indirectly (e.g., 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 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 a single facet value by interacting with breadcrumbs breadcrumbFacet facetId, facetTitle, facetValue
    Clear all selected facet values by interacting with breadcrumbs breadcrumbResetAll N/A
  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>

    where:

    • <TYPE> (string) is the facet data type (e.g., specific).

    • <FIELDNAME> (string) is the @-prefixed name of the field that this facet is based on (e.g., @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 (e.g., @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 (e.g., Hardcover).

  4. Set other required/optional properties as appropriate (language, originLevel1, originLevel2, etc.).

When the Usage Analytics Write API returns successfully:

  1. Update all of the facets in the search interface.

  2. Update the result list.

Example

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
    }
  ],
  ...
}

A Search event is logged for this action:

POST https://platform.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"
      }
    ]
  }
]

Requesting 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 end user to autonomously request to display more values in a facet. When the end 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 end 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 (e.g., @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 (e.g., 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.

    Although this query isn’t meant to return actual result items, you should include the other search request parameters as usual so that it represents 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/occurrence counts.

  8. Call the Search API to execute this query.

When the Search API returns, call the Usage Analytics 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 (e.g., @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 (e.g., @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 (e.g., Book Format).

  5. Set other required/optional properties as appropriate (language, originLevel1, originLevel2, etc.).

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

Example

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
    }
  ],
  ...
}

A Custom event is logged for this action:

POST https://platform.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"
      }
    ]
  }
]

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 this way, on each valid keystroke:

  1. Retrieve the current value from the facet search input (e.g., a).

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

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

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

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

    4. Set other optional properties as appropriate (ignoreValues, captions, etc.).

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

    Although this query isn’t meant to return actual result items, you should include the other search request parameters as usual so that it represents 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/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.

Example

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 authors name into the facet search box. This interaction executes the following request:

POST https://platform.cloud.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
}

These guidelines suggest a facet search implementation which is based on that of the 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.

Recommended Articles