Filter State Management#
Filter state in Langfuse table components is split across three cooperating layers: a sidebar filter system (explicit user filters + implicit defaults), a date/time range system (with cross-view persistence), and an external override pattern used by embedded preview tables.
Architecture Overview#
externalFilterState (prop, optional)
│
│ wins when present
▼
useSidebarFilterState ──► effectiveFilterState ──┐
│ (+ implicit defaults) │
▼ ├──► filterState ──► API query
explicitFilterState ───────────────────────────── ┘ (+ dateRangeFilter, userIdFilter, etc.)
│
└── useQueryFilterState (URL params + session storage)
useTableDateRange / useGlobalDateRange ──► dateRange (externalDateRange wins if set)
Every major table — TracesTable, ObservationsTable, EventsTable — follows the same pattern.
External Filter State Override#
Tables accept externalFilterState?: FilterState and externalDateRange?: TableDateRange props for embedding scenarios (e.g., showing a scoped trace list inside a user detail page).
When either is present, it completely replaces the internally-built filter state:
// TracesTable (line 347)
const filterState = externalFilterState || combinedFilterState;
// EventsTable (line 421)
const filterState = externalFilterState || combinedFilterState;
For the date range the same pattern applies — externalDateRange is preferred over the table's own useTableDateRange result:
const dateRange = externalDateRange ?? tableDateRange;
When hideControls=true (the embedded-preview flag), URL persistence is also disabled via disableUrlPersistence: hideControls in the useSidebarFilterState call .
Internal Filter State (useSidebarFilterState)#
useSidebarFilterState manages the sidebar filter UI and returns three states:
| Property | Meaning |
|---|---|
explicitFilterState | Filters the user explicitly set (persisted to URL/session storage) |
effectiveFilterState | Explicit filters + implicit defaults (e.g. environment defaults, hidden environments); used for API queries |
Implicit environment defaults are injected via implicitDefaultConfig: DEFAULT_SIDEBAR_IMPLICIT_ENVIRONMENT_CONFIG and are excluded from URL serialization to keep shared links clean .
The toolbar displays explicitFilterState (user-visible chip count), while API queries use effectiveFilterState (includes implicit defaults) .
Query/URL Filter State (useQueryFilterState)#
useQueryFilterState is a lower-level hook that encodes/decodes FilterState to URL query parameters and session storage using a pipe-delimited format. Tables use it to seed a type-specific default filter (e.g. the generation type filter) that is bypassed when a saved view is loaded .
Date Range Persistence#
useTableDateRange returns a timeRange and setTimeRange used by every table. The time range is converted to an absolute TableDateRange ({ from, to }) via toAbsoluteTimeRange and then assembled into startTime date filter conditions before being merged into combinedFilterState .
Cross-view persistence (June 2026+)#
PR #14535 introduced useGlobalDateRange and the resolveTimeRange utility to share one date range across all views. useTableDateRange and useDashboardDateRange are now thin wrappers over useGlobalDateRange .
Resolution rule (presence-XOR) :
- URL
?dateRange=present → URL wins (deep links reproduce exactly) - URL absent → read per-project
localStorage(survives browser restarts) - Default is never auto-written to URL (shared links stay clean)
- A stored preset not in a view's
allowedRangesdegrades to that view's fallback
Filter Operator Semantics & URL Safety#
Facet exclusion: none of vs. any of#
PR #14939 changed how deselecting a sidebar facet value is persisted. Previously, unchecking one value materialized any of [all-other-N-options] — which could exceed HTTP header limits (431) and produced wrong results for multi-valued columns. Deselects from the implicit-all state now persist a compact none of [unchecked] via resolveCheckboxOperator .
URL safety cap#
Serialized filter state above 4,000 chars is dropped from the URL but kept in session storage. Same-tab refreshes still work; copied links degrade to "no filter" instead of a 431 blank page .
Search bar sync#
The filter sidebar and search bar share explicitFilterState. PR #14355 fixed sync issues by ensuring the managed environment policy strips only the system-shaped none of [hidden] default, never user-authored selections .
Key Source Files#
| File | Role |
|---|---|
useSidebarFilterState.tsx | Main sidebar filter hook — UI state, operators, persistence, environment policy |
useFilterState.ts | Low-level URL + session storage encoding for FilterState |
useTableDateRange.tsx | Table date range hook (delegates to useGlobalDateRange) |
date-range-utils.ts | TableDateRange, TimeRange, resolveTimeRange, toAbsoluteTimeRange, serialization helpers |
TracesTable | Reference implementation — traces |
ObservationsTable | Reference implementation — observations/generations |
EventsTable | Reference implementation — events |