Skip to content

Implement Faceted Search

⏱ 30 minutes intermediate
📜CoreGraph

Faceted search lets users narrow results by selecting values from categories — such as filtering articles by topic, date range, or author. Graph calculates facet counts server-side, so your frontend shows accurate counts without fetching all results and computing them locally.

Graph supports three facet types depending on the property’s data type:

Facet typeProperty typeExample
String facetString, select fieldsCategory, Author, Tags
Date facetDateTime fieldsPublishDate bucketed by month
Number rangeNumeric fieldsPrice ranges, rating buckets

Request facets alongside your search results. Facets appear in a separate facets section of the response.

String facets
graphql
query ArticlesWithFacets {
  ArticlePage(
    where: { _metadata: { status: { eq: "Published" } } }
    limit: 20
  ) {
    items {
      Headline
      Author
      Category
    }
    total
    facets {
      Category(limit: 10, orderBy: COUNT, orderType: DESC) {
        name
        count
      }
      Author(limit: 10) {
        name
        count
      }
    }
  }
}
json
{
  "data": {
    "ArticlePage": {
      "items": [ ... ],
      "total": 156,
      "facets": {
        "Category": [
          { "name": "Technology", "count": 42 },
          { "name": "Business", "count": 38 },
          { "name": "Science", "count": 27 }
        ],
        "Author": [
          { "name": "Jane Smith", "count": 15 },
          { "name": "John Doe", "count": 12 }
        ]
      }
    }
  }
}

Verify: Run this query in the Graph explorer. You should see a facets object in the response with Category and Author arrays, each containing name and count values. If facets return empty, confirm your content type has published items with those fields populated.

Date facets bucket results by time units. Use these for “published this month” or “by year” filters.

Date facets with time buckets
graphql
query ArticlesByDate {
  ArticlePage(
    where: { _metadata: { status: { eq: "Published" } } }
    limit: 20
  ) {
    items {
      Headline
      _metadata { published }
    }
    facets {
      _metadata {
        published(unit: MONTH) {
          name
          count
        }
      }
    }
  }
}

Available date units: DAY, WEEK, MONTH, YEAR.

Verify: The response should show date buckets with counts. If you use MONTH, each name value will be a month string like "2026-03". If all counts are zero, check that your content type’s DateTime field has values.

Number ranges let users filter by price tiers, rating brackets, or other numeric groupings.

Number range facets
graphql
query ProductsWithPriceRanges {
  ProductPage(
    where: { _metadata: { status: { eq: "Published" } } }
    limit: 20
  ) {
    items {
      ProductName
      Price
    }
    facets {
      Price(
        ranges: [
          { from: 0, to: 25 }
          { from: 25, to: 50 }
          { from: 50, to: 100 }
          { from: 100, to: 500 }
        ]
      ) {
        name
        count
      }
    }
  }
}

Verify: Each range should show a count of items whose numeric value falls within the defined bounds. The name field shows the range label (e.g., "0 - 25"). If ranges overlap, items will be counted in multiple buckets.

Step 5: Combine facets with active filters

Section titled “Step 5: Combine facets with active filters”

When a user selects a facet value, add it as a filter to the query while continuing to return facet counts for remaining options.

Filtering with facet selection
graphql
query FilteredWithFacets($selectedCategory: String) {
  ArticlePage(
    where: {
      _metadata: { status: { eq: "Published" } }
      Category: { eq: $selectedCategory }
    }
    limit: 20
  ) {
    items {
      Headline
      Category
      Author
    }
    total
    facets {
      Category(limit: 10) {
        name
        count
      }
      Author(limit: 10) {
        name
        count
      }
    }
  }
}
javascript
async function fetchWithFacets(selectedFilters) {
  const where = {
    _metadata: { status: { eq: 'Published' } }
  };

  if (selectedFilters.category) {
    where.Category = { eq: selectedFilters.category };
  }
  if (selectedFilters.author) {
    where.Author = { eq: selectedFilters.author };
  }

  const result = await graphFetch(FACETED_QUERY, { where });

  return {
    items: result.data.ArticlePage.items,
    total: result.data.ArticlePage.total,
    facets: result.data.ArticlePage.facets,
  };
}

Verify: With a category filter applied, the total count should be lower than without. Facet counts for the filtered field should still show all options (so users can switch), but other facets should reflect the narrowed set. If facet counts don’t change when you filter, check that the where clause is correctly applied.

Section titled “Step 6: Combine facets with full-text search”

Facets work alongside full-text search. The facet counts reflect the search-filtered result set, not the entire content collection.

Search with facets
graphql
query SearchWithFacets($searchTerm: String!) {
  ArticlePage(
    where: {
      _fulltext: { contains: $searchTerm }
      _metadata: { status: { eq: "Published" } }
    }
    orderBy: { _ranking: RELEVANCE }
    limit: 20
  ) {
    items {
      Headline
      Category
      _score
    }
    total
    facets {
      Category(limit: 10) {
        name
        count
      }
    }
  }
}

Verify: Search for a specific term and check that facet counts reflect only the matched results, not the entire content set. The _score field should appear on each item, confirming relevance ranking is active.

Troubleshooting: If facets return empty arrays, common causes are:

  • The property is not indexed in Graph (check your content model)
  • No published content has values for that field
  • The field name in the facets block doesn’t match the property name exactly (case-sensitive)
  • Limit facet values — Use limit to cap facet options at a reasonable number (10-20). Users cannot meaningfully choose from hundreds of options.
  • Order facets by count — Show the most common values first using orderBy: COUNT, orderType: DESC.
  • Show selected filters clearly — Display active filter selections above the results so users can remove them easily.
  • Update facets on filter change — Always re-query when the user selects or deselects a facet value. Facet counts must reflect the current filter state.