Filters and Facets

Facets are the structured-data sidekicks of free-text search — checkboxes, sliders, and date ranges that let the user narrow results without re-typing a query. Curiosity supports three facet shapes, each with a different cost/value trade-off.

Property facets

Driven by a property directly on the node, and configured under Manage → Search → Facets (#/manage/search/facets) → Property Facets (or as the Facets tab of the Indexes settings page at #/manage/data/indexes — the same configuration, reached from wherever you happen to be).

The page is segmented by the field's data type, because the type decides what kind of facet the field produces. Each segment lists the schema fields of that type together with the indexes already configured for them:

Segment Field types What the facet looks like
Text string, Language One facet value per distinct text value. Best for a small set of repeated values (a status, a category, a country) — a field with thousands of distinct values produces a facet nobody can use.
Yes / No bool A two-value facet, so users can narrow to the nodes where the flag is set or not set.
Time date and time fields A range facet, with values grouped into buckets. The bucket size (day, month, year, …) is the index's Time Quantization Strategy — open the advanced settings of a configured field to change it.
Numeric numeric fields A range facet, bucketed. The bucket size is the index's Quantization Strategy; a numeric facet can also bucket by a custom expression instead of by a fixed order of magnitude.

A facet on a field does not make its text content searchable — it only filters by it. It can also speed up a query that would otherwise filter with a .Where("FieldName", f => f.StartsWith("Value")) clause. To index a field without exposing it as a facet, set the field's index to No Facet.

The field selector in the index editor only offers the field types the chosen index accepts, so a field cannot be configured with an index that cannot serve it.

Choosing a good property to facet on:

Property example Good facet? Why
SupportCase.Status Yes Small set ("Open", "Pending", "Resolved").
Article.Category Yes Curated, small set.
SupportCase.IsEscalated Yes A bool — the Yes / No segment.
Article.Updated Yes A date field, bucketed by the index's time quantization. Prefer the global time filter when you only need "recent".
SupportCase.Summary No Unique per row — facet would explode.

The differentiator versus most search engines. A related facet filters by an edge to a normalized entity node — e.g. "cases for devices made by Apple".

flowchart LR Query[SupportCase] -->|ForDevice| Device Device -->|MadeBy| Manufacturer Filter[User picks 'Apple'] -.-> Manufacturer

Two reasons to use related facets instead of duplicating the field on the node:

  1. No denormalization. If a device changes manufacturer, all cases follow automatically.
  2. Multi-hop. Filter on something two or three edges away (manufacturer of the device of the case).

Configure under Manage → Search → Facets → Related Facets.

Empty facets are hidden

A related facet is only rendered when at least one node in the current result set actually has a matching edge. If you've just configured a facet and it doesn't appear, the most common cause is that none of the visible results connect to a node of the target type. When testing a new related facet, ingest (or link) a handful of nodes on both sides of the edge before expecting the facet to show up.

From code:

var req = SearchRequest.For("screen flicker");
req.BeforeTypesFacet = new HashSet<string> { N.SupportCase.Type };

req.RelatedFacets = new Dictionary<string, List<RelatedFacet>>
{
    ["Manufacturer"] = new()
    {
        new RelatedFacet { TargetType = "Manufacturer", TargetKey = "Apple" }
    }
};

var query = await Graph.CreateSearchAsUserAsync(req, CurrentUser, CancellationToken);
return query.Emit();

Time filter

Every node carries a Timestamp (or Time) field. The workspace exposes a global time filter (week, month, custom range) that applies to any indexed node type.

Exclude reference types from the time filter — Manufacturer, Status, Category rarely change and would show up in every time window:

  • Manage → Search → Search (#/manage/search/settings) → Advanced Settings → Excluded from Time

Value facets

Driven by a numeric range — score buckets, age ranges, prices. Configured the same way as property facets but rendered as a range slider.

Performance tips

  • Cardinality matters. Facets over millions of distinct values become slow to count. Pre-aggregate by binning if necessary.
  • Pre-compute multi-hop facets. If a related facet walks three hops, consider materializing the relationship onto the source node during ingestion so the facet is one hop.
  • Lazy load facet values. The UI defaults to top-N values per facet. Don't expose Show all for high-cardinality facets in production.
© 2026 Curiosity. All rights reserved.