---
title: Aggregate Optimizely Graph results with the C# SDK
slug: cms-13/docs/aggregations
createdAt: 2026-07-30T20:22:07.530Z
---

Use `.Facet()` to retrieve value counts for one or more fields alongside query results. Facets power category navigation, filter-by-author panels, and other UI patterns that require aggregated counts.

## Retrieve facet counts

Request value counts for one or more fields to display in a filter panel or navigation sidebar.

```csharp
var result = await client
    .QueryContent<BlogPostPage>()
    .Facet(x => x.Author!)
    .Facet(x => x.ViewCount!)
    .GetAsContentAsync();
```

Read the counts back from the result:

```csharp
var authorFacet = result.Facets?.GetFacet(x => x.Author);
var viewCountFacet = result.Facets?.GetFacet(x => x.ViewCount);
```

Each facet entry contains a value and its count.

## Use facets with full-text search

Facet counts reflect the current search results, not the full content set:

```csharp
var result = await client
    .QueryContent<BlogPostPage>()
    .SearchFor("api")
    .UsingFullText()
    .Facet(x => x.ViewCount)
    .GetAsContentAsync();
```

## Filter within facets

Pass `filters` to apply a value filter while keeping the full facet list. This pattern is essential for faceted navigation. Users refine or change their selection without losing the other options.

```csharp
var result = await client
    .QueryContent<BlogPostPage>()
    .Facet(x => x.Author, filters: ["John Developer"])
    .GetAsContentAsync();
```

The result set is filtered to posts by `John Developer`, but the facet still shows counts for all authors so the user can switch.

## Multi-select facets

To let users select multiple values within a single facet, use `OR` logic. Selections across multiple facets combine using `AND` logic. Pass an empty string (`""`) as a placeholder for facets with no active selection:

```csharp
var result = await client
    .QueryContent<BlogPostPage>()
    .Facet(x => x.BlogCategory!, filters: [""])
    .Facet(x => x.Author!, filters: ["John Developer"])
    .GetAsContentAsync();

var categoryFacet = result.Facets?.GetFacet(x => x.BlogCategory);
var authorFacet = result.Facets?.GetFacet(x => x.Author);
```

## Filter boolean facets

Use `FacetSelected` to add a facet on a `bool` property and optionally filter the results to one value. Pass `true` or `false` to filter the results to that value, or `null` to add the facet without a filter. The facet still reports counts for both values, so the unselected value stays available to users.

```csharp
var result = await client
    .QueryContent<ProductPage>()
    .FacetSelected(x => x.InStock, selected: true)
    .GetAsContentAsync();
```

For an untyped query, pass the field name as a string, such as `client.Query("ProductPage").FacetSelected("InStock", selected: false)`.

:::hint{type="info"}
**Note**
In CMS 13.1.3 and later, the `Facet` overloads that take a non-nullable `bool selected` parameter are obsolete. These overloads cannot filter results to `false`, because they treat `selected: false` the same as no filter. The obsolete overloads still work: `selected: true` filters the results to `true`, and `selected: false` applies no filter. Before 13.1.3, `selected: true` made Optimizely Graph reject the query. Replace these calls with `FacetSelected`, and pass `null` where you do not want a filter.
:::

## Read facet configuration from results

In CMS 13.1.3 and later, `Facets.Items` returns a `FacetFieldResult` for each faceted field in the response. Each result holds the facet values and the facet configuration that the query requested. Use this configuration to tell filtered facets from unfiltered facets without hard-coding field names.

```csharp
var result = await client
    .QueryContent<BlogPostPage>()
    .Facet(x => x.Author, filters: ["John Developer"])
    .Facet(x => x.BlogCategory)
    .GetAsContentAsync();

foreach (var facet in result.Facets?.Items ?? [])
{
    foreach (var value in facet.Values)
    {
        var isSelected = facet.Filters.Contains(value.Name);
        // Render value.Name, value.Count, and the selected state.
    }
}
```

`FacetFieldResult` has the following properties:

- `FieldName` – The name of the faceted field. Nested paths use dot notation, such as `Composition.Type`.
- `Values` – The facet values and their counts.
- `HasFilter` – `true` when the query requested the facet with one or more filter values, including a boolean facet filtered to either value. `false` for plain terms facets, date and time facets, numeric range facets, and fields that the query did not request.
  A placeholder filter, such as `filters: [""]` in the multi-select pattern, also sets `HasFilter` to `true`.
- `Filters` – The filter values the query requested, in the same format as `FacetValue.Name`, so you can compare them directly. Empty when the query requested no filters.
- `Selected` – `true` when the query filters a boolean facet to `true`. To tell a boolean facet filtered to `false` from an unfiltered facet, check `Filters`.
- `Limit` – The limit the query requested for the facet, or `null`.

Filters narrow the returned items, not the facet values, so use `Filters` to mark which values the user selected. `Facets.Items` does not replace `GetFacet`, `HasFacet`, or `FieldNames`.
