Active Foundation

Search documentation

Search all pages, headings and text, in the current package or topic or in all documentation.

Filtering

Filter types, operators, presets, date filters with time zones, and the global search.

@workspace/active-grid 11.0.0Status unknowncontent differs from the recorded release 11.0.0baseline ce081dc3

The package's own guide

This guide is the package's docs/guides/filters.md, included unchanged from the package as it is in Foundation. Links to the package's internal design documents keep their text.

How to configure column filters in Active Grid: filter types, operators, presets, date filters (including time zones and calendar locales) and the global search. Type signatures, operator factories and filter components are in the API reference: ../api/filters.md.

Filter Types#

Active Grid provides the following standard filter widgets, configured via meta.filterType on column definitions:

TypeComponentOperatorsUse Case
textTextFilter8 operatorsNames, descriptions, codes
numberNumberFilter7 default operatorsAmounts, quantities, percentages
dateDateFilter4 default operatorsDates and timestamps
selectSelectFilterMulti-selectEnum values, categories, statuses
booleanBooleanFilter1 default operatorYes/no flags

filterType chooses the widget; the capability decides whether there is one at all. Both the filter row and each filter cell require column.getCanFilter() as well. In client mode that is true by default, so the table above reads as it always did. In server mode a column without a server.filter mapping (or without an entry in the serverCapabilities filter map) renders no control, whatever its filterType says — including a filterType injected by a column type preset. Presentation may narrow the server contract, never widen it. See ../api/columns.md § Filtering.

An additional interval type accepts numeric from/to bounds and a within/overlap mode through IntervalFilterValue; its host supplies the interval input UI. The tables below include optional absence operators, which are not in the default number/date/boolean lists (see withEmpty).

Text Operators#

OperatorLabelRequires ValueDescription
containsContainsYesCase-insensitive substring match
notContainsDoes not containYesInverse of contains
equalsEqualsYesExact match (case-insensitive)
notEqualsDoes not equalYesInverse of equals
startsWithStarts withYesPrefix match
endsWithEnds withYesSuffix match
isEmptyIs emptyNonull, undefined, or ''
isNotEmptyIs not emptyNoInverse of isEmpty

Number Operators#

OperatorLabelRequires ValueDescription
equalsEqualsYesExact numeric match
notEqualsDoes not equalYesInverse of equals
greaterThanGreater thanYesStrictly greater
greaterThanOrEqualGreater than or equalYesGreater or equal
lessThanLess thanYesStrictly less
lessThanOrEqualLess than or equalYesLess or equal
betweenBetweenYes (two values)Inclusive range: value >= from && value <= to
isEmptyIs emptyNonull or undefined
isNotEmptyIs not emptyNoInverse of isEmpty

Date Operators#

OperatorLabelRequires ValueDescription
onDateOn dateYesExact date match (day precision)
beforeBeforeYesStrictly before the given date
afterAfterYesStrictly after the given date
inRangeIn rangeYes (two values)Between start and end date (inclusive)
isEmptyIs emptyNonull or undefined
isNotEmptyIs not emptyNoInverse of isEmpty

Boolean Operators#

OperatorLabelRequires ValueDescription
equalsIsYesMatch true or false
isEmptyIs emptyNonull or undefined
isNotEmptyIs not emptyNoInverse of isEmpty

Select Filter#

The select filter uses a multi-select dropdown rather than operators. Users select one or more values from a list.

Filter value is an array of selected values: ['active', 'pending'].

Options come from (in priority order):

  1. meta.filterOptions — Explicit options array.

  2. Server-side facets — Returned by fetchFn in server mode.

  3. Client-side faceted values — Auto-computed from column data.

Configuration:

typescript
{  meta: {    filterType: 'select',    filterOptions: [      {label: 'Active', value: 'active'},      {label: 'Inactive', value: 'inactive'},    ],    filterSearchable: true,      // Enable search within options    filterShowSelectAll: true,    // Show "Select all" toggle  },}

FilterValue Type#

Filter values are discriminated unions based on the operator:

typescript
// No-value operators (isEmpty, isNotEmpty){operator: 'isEmpty'}
// Single-value operators{operator: 'contains', value: 'john'}{operator: 'greaterThan', value: 50000}{operator: 'onDate', value: '2025-06-15'}{operator: 'equals', value: true}
// Range operators (between, inRange){operator: 'between', value: 1000, valueTo: 5000}{operator: 'inRange', value: '2025-01-01', valueTo: '2025-12-31'}

TypeScript enforces the shape through a discriminated union; the type definitions are in ../api/filters.md § FilterValue Types.

Operator Presets#

A preset restricts the operators a column offers. Select one by name with meta.operatorPreset:

typescript
{meta: {filterType: 'text', operatorPreset: 'identity'}}

What a preset can and cannot do#

A preset only ever NARROWS. It is applied as a filter over the operators the column already offers — the per-type defaults on a client-mode grid, or the server-declared capability list on a resource-backed one. It can therefore never add an operator the server does not allow, and an unknown or unusable preset can never widen the offer either. This is the property that makes a custom preset safe to accept from configuration.

The consequence is worth stating plainly: a preset that names an operator the column does not have available is a no-op for that operator, not an opt-in. See withEmpty for the case where that matters.

Built-in presets#

NameNarrows toUse for
identityequals, notEqualsIDs, codes, enum values.
searchOnlycontainsA column that behaves like a search bar.
rangeOnlygreaterThan, lessThan, between, greaterThanOrEqual, lessThanOrEqualStatistics, years, measurements.
comparisonequals plus the four comparisonsNumeric filters where exact match is also relevant.
noEmptyeverything except isEmpty / isNotEmptyColumns where "no value" is not a meaningful question.
withEmptycomparisons plus isEmpty / isNotEmptyNullable number/date columns — see the note below.

withEmpty#

isEmpty / isNotEmpty are deliberately absent from the default operator set for number, date and boolean columns (the rationale is on getTextOperators in types/filters.ts). withEmpty is the opt-in for the rare nullable column where the question does make sense.

It only takes effect where the operator is already available, i.e. on a resource-backed grid whose capability declares isEmpty for that column. On a plain client-mode grid the base list does not contain isEmpty, and a preset narrows rather than widens, so the preset changes nothing there. The same limit applies to meta.allowedOperators.

Use it only on columns whose underlying field is actually nullable (String?, Int?, DateTime?). On a non-nullable field the query builder in @workspace/active-resources does not build a null branch: a text column then matches the empty string only, and for number, date and boolean the operator allowlist rejects isEmpty before a condition is built at all. Declaring nullable: true on a field that is not nullable is what validateRegistry refuses.

Declaring your own presets#

<GridDefaults> may override a built-in preset and declare additional ones. The map is merged per key with the built-ins, so adding one preset never removes the others:

TSX
<GridDefaults  filtering={{    operatorPresets: {      // Override a built-in: every column that says `identity` follows this.      identity: ['equals'],      // Add one of your own.      codeLookup: ['equals', 'startsWith'],    },  }}>  <App /></GridDefaults>
typescript
{meta: {filterType: 'text', operatorPreset: 'codeLookup'}}

The built-in names keep autocomplete on meta.operatorPreset; an additional name is an ordinary string. The union of built-in names is exported as BuiltInOperatorPreset for consumers writing shared column factories. Widening that one field does not loosen the rest of the column meta — filterType and allowedOperators stay closed unions.

There is no way to remove a built-in preset. Removing one would break the columns that name it.

How a bad preset behaves#

Every case is explicit, dev-warned, and fails towards "no narrowing":

SituationResult
The name is not a known presetThe column keeps its full operator list. A preset can only narrow, so an unknown name can never widen the offer.
An entry is not a filter operator (a typo)That entry is dropped; the rest of the preset applies.
The same operator appears twiceDeduplicated, keeping the first position.
The preset is empty, or every entry was badRefused. An empty narrowing would leave the filter with nothing to choose.
The preset names an operator the server forbidsIntersected away. Narrowing cannot widen a capability.

Warnings appear in development only (process.env.NODE_ENV !== 'production') and are emitted once per column and reason.

Custom Operator Restriction#

For fine-grained control on a single column, use meta.allowedOperators:

typescript
{  meta: {    filterType: 'number',    allowedOperators: ['greaterThan', 'lessThan', 'between'],    defaultFilterOperator: 'greaterThan',  },}

Enabling Operator Selection#

By default, filters use a single default operator. To show the operator selector dropdown:

typescript
{meta: {filterType: 'text', enableFilterOperators: true}}

Default operators per type (when enableFilterOperators is false):

  • Text: contains

  • Number: equals

  • Date: onDate

  • Boolean: equals

Date Filter Configuration#

Presentation and time zone#

The host supplies the reader's presentation through locale, including an explicit time zone for relative dates and calendars. The grid does not resolve application settings. Labels come from the host translator; date notation and calendar locales delegate to @workspace/active-i18n.

TSX
const presentation = {languageLocale: 'en-GB', timeZone: 'UTC'};
<ActiveGrid  mode="client"  gridId="measurements"  locale={presentation}  data={measurements}  columns={columns}/>;

Keep an object presentation stable across renders. The example chooses UTC explicitly; a real host supplies the time zone appropriate to its data view.

Presets#

DEFAULT_DATE_PRESETS includes today, yesterday, last 7 days, last 30 days, this month, last month, this quarter and this year. Their getValue receives the grid presentation. The relative day comes from its explicit timeZone; a missing zone throws a configuration error rather than using the browser's.

The returned Date carries calendar-day fields at local midnight. The date filter reads those fields into yyyy-MM-dd strings. These are calendar bounds, not UTC timestamps. Do not convert them to instants before giving them back to the date filter.

A column may supply its own meta.dateFilterPresets, or false to hide the preset list:

typescript
// Disable presets entirely — calendar only{meta: {filterType: 'date', dateFilterPresets: false}}

A custom DateFilterPreset.getValue(presentation?) returns {from: Date, to?: Date} and runs when selected. A fixed calendar range can be concrete without assuming a time zone:

typescript
import type {DateFilterPreset} from '@workspace/active-grid';
const reviewPeriod: DateFilterPreset = {  key: 'records.reviewPeriod',  label: t('records.reviewPeriod'),  type: 'range',  getValue: () => ({    from: new Date(2026, 0, 1),    to: new Date(2026, 2, 31),  }),};

For a relative custom preset, obtain the calendar day through the central active-i18n date helpers using the explicit presentation zone, never from the browser clock (new Date() plus date arithmetic reads the runtime's zone). The host owns what the range means; the grid owns its filter representation.

Range Mode#

There are three ways to enable date range filtering:

1. Operator-driven range (recommended) — set enableFilterOperators: true. The calendar switches to range mode automatically when the user selects the inRange operator. All other operators (onDate, before, after) show a single date picker.

typescript
// Operator dropdown controls the mode — user picks "In range" for range selection{meta: {filterType: 'date', enableFilterOperators: true}}

2. Forced range mode — set dateFilterMode: 'range'. The calendar is always in range mode. Use for financial reporting or period selection where you always want a date range, without exposing the operator dropdown.

typescript
// Always show range calendar — no operator selector needed{meta: {filterType: 'date', dateFilterMode: 'range'}}
// Range with date bounds{meta: {filterType: 'date', dateFilterMode: 'range',        dateFilterMinDate: '2020-01-01', dateFilterMaxDate: '2026-12-31'}}

Note: Do not combine enableFilterOperators: true with dateFilterMode: 'range' — the forced range-mode overrides the operator-driven switching, making the operator-dropdown's inRange/single-date distinction ineffective. Use one or the other.

3. Toggle mode — set dateFilterMode: 'both'. Shows a toggle that lets the user switch between single-date and range mode.

Manual Date Entry#

The date filter includes a text input field above the calendar. Users type the numeric date pattern of the grid's presentation (getDateInputPattern, active-i18n): dd-MM-yyyy for nl, dd.MM.yyyy for de, yyyy/MM/dd for ja-JP, and so on for any locale the formatters accept. Every Unicode digit set is read (٠٩-٠٣-٢٠٢٦ is 9 March 2026); no textual or localized date grammar is accepted beyond that pattern.

The text input and calendar are bidirectionally synced. In range mode, two text inputs are shown ("from" and "to").

Calendar locale#

getDateFnsLocaleForGrid(presentation) delegates to active-i18n's composed locale: language determines words and presentation preferences determine notation. Active-grid has no separate language registry, and the calendar refuses no locale the formatters accept:

  • month and weekday names come from that locale (derived from Intl for a tag without bundled date-fns wording), always in the Gregorian calendar;

  • the accessible labels (previous and next month, today, week) come from Intl (getCalendarPhrases);

  • day numbers are written in the locale's digits where the calendar library supports that numbering system;

  • the week starts where Intl.Locale week info says, and on Monday when the runtime has none.

A tag the formatters refuse (malformed, without Intl data, or asking for a non-Gregorian calendar) throws active-i18n's UnsupportedLocaleError.

The calendarLocale prop supplies a date-fns locale-object override for the calendar. It does not replace the grid's value presentation or its explicit time zone. If the object carries its own labels (a react-day-picker/locale import does), those labels win over the Intl-derived ones. For a custom calendar, derive it from the grid's effective presentation:

TSX
import {useGridLocale} from '@workspace/active-grid';import {getDateFnsLocale} from '@workspace/active-format';
function CustomDateInput() {  const calendarLocale = getDateFnsLocale(useGridLocale());  // Use calendarLocale in the custom calendar.}

The built-in date filter performs this resolution itself. Date input parsing supports the package's declared formats; a calendar library's wider range of calendars, layouts or parsing conventions is not automatically a grid feature. The internal owners are src/utils/date-filter-presets.ts, src/utils/date-locale.ts and src/components/filters/date-filter.tsx.

The global search (toolbar) uses a 5-level fuzzy matching algorithm:

LevelStrategyExample: searching "jhn"
1Exact match"jhn" matches "jhn"
2Starts with"jhn" matches "jhnson"
3Word boundary"jhn" would not match (no word starts with "jhn")
4Contains"jhn" would not match (not a substring)
5Sequential characters"jhn" matches "j-o-h-n" (j...h...n found in sequence)

Fuzzy search is enabled by default. Disable via GridConfig.behavior.enableFuzzyFilter:

TSX
<GridDefaults behavior={{enableFuzzyFilter: false}}>

Server-Side Filtering#

In server mode, filter values are passed directly to the fetchFn in params.filters. The grid does not evaluate filters client-side — the server is responsible for applying them.

Column filter values arrive as FilterValue objects (with operators) or as arrays (for select filters). See server-mode.md for implementation details.

A date filter sends calendar strings in value and, for a range, valueTo. The resource server validates and interprets those values according to the server field's contract; a JavaScript Date from a preset is not forwarded unchanged as a UTC timestamp. Calendar-day columns and timestamp columns have different semantics: if a server query needs an instant interval, its server owner derives the interval in the declared zone. Do not make the browser's zone an implicit query owner.

Operator Labels#

Operator labels come from the host translator. The operator factories (getTextOperators(t), getDateOperators(t), getOperatorsForType(type, t) and the rest) take it as their argument; the static *_OPERATORS arrays carry English defaults and apply only when no translator is supplied. A supplied translator is authoritative, including any raw key it returns. The factories, the arrays and the label keys are listed in ../api/filters.md § Operator Arrays + Factories.

Boolean Filter Variants#

The boolean filter supports two display modes.

Text variant (default)#

Three text buttons: All / Yes / No.

typescript
{meta: {filterType: 'boolean', booleanFilterVariant: 'text'}}

Custom labels:

typescript
{  meta: {    filterType: 'boolean',    booleanFilterLabels: {      all: 'Any',      true: 'Enabled',      false: 'Disabled',    },  },}

Icon variant#

Three icon buttons: — / ✓ / ✗.

typescript
{meta: {filterType: 'boolean', booleanFilterVariant: 'icon'}}

This page as Markdown