Filtering

Built-in per-column filter popups for text, number, date, datetime, and time-of-day columns, with operator selection and two-condition AND/OR logic. Categorical columns can use a set filter instead, picking values from a checklist rather than typing an operator.

Column filters: text, number, and date

Click the filter icon in any column header. Pick an operator, enter a value, then click Apply. Try salary ≥ 130000, or filter hired Before 2021-01-01.

Name
Department
Salary
Hired
Aria Chen
Engineering
$155,000
2019-03-12
Avery Johnson
Sales
$104,000
2021-08-14
Blake Turner
Product
$127,000
2019-11-25
Casey Park
Design
$109,000
2022-11-19
Dakota Silva
Engineering
$152,000
2019-07-08
Devon Wright
Engineering
$158,000
2016-12-01
Drew Santos
Product
$138,000
2020-03-30
Elliot Ramos
Design
$121,000
2021-12-13
Emerson Cole
Analytics
$136,000
2020-09-11
Finley Grant
Analytics
$147,000
2017-11-29

Filtering runs on the full dataset before pagination, so a filter matches rows on every page, not just the one you are viewing. The result count and page navigation update to the filtered set. This applies to client-side pagination; see theserver-side recipe for the server case.

Quick start

Add filterable: true and a stable id to any column. A filter icon appears in the column header. Filters across columns combine with AND. A row must pass every active filter to appear.

const columns: TableColumn<Row>[] = [{ id: 'name', name: 'Name', selector: r => r.name, filterable: true }];

<DataTable columns={columns} data={data} />;

Filter types

Set filterType to get the right operator set and input widget. It defaults to "text". Five of the six types work the same way, an operator plus a value you type;"set" is the exception and shows a checklist instead.

const columns: TableColumn<Row>[] = [
  { id: 'name', name: 'Name', selector: r => r.name, filterable: true },
  { id: 'score', name: 'Score', selector: r => r.score, filterable: true, filterType: 'number' },
  { id: 'dob', name: 'Birth date', selector: r => r.dob, filterable: true, filterType: 'date' },
  { id: 'seen', name: 'Last seen', selector: r => r.seen, filterable: true, filterType: 'datetime' },
  { id: 'ranAt', name: 'Ran at', selector: r => r.ranAt, filterable: true, filterType: 'time' },
];
filterTypeDefault operatorInputOperators
"text" (default)ContainsTextContains, Does not contain, Equals, Does not equal, Begins with, Ends with, Blank, Not blank
"number"EqualsNumberEquals, Does not equal, Greater than, ≥, Less than, ≤, Between, Blank, Not blank
"date"EqualsDateEquals, Before, After, Between, Blank, Not blank
"datetime"EqualsDate & timeEquals, Before, After, Between, Blank, Not blank
"time"EqualsTimeEquals, Before, After, Between, Blank, Not blank
"set"n/aChecklistNone, pick values from a list

A few types have behavior worth knowing before you pick one:

  • "date" matches a whole calendar day, so Equals finds a row recorded at any time that day. "datetime" matches an exact instant, down to the minute.
  • "time" ignores the date and compares only the time of day, so it filters across every date at once. Handy for logs: “anything after 17:00”, or “errors between 02:00 and 04:00”. A Between that starts later than it ends wraps past midnight, so 22:0006:00gives you an overnight window.
  • Between gives you two inputs and includes both bounds. Fill in only one to leave that side open, so a number filter with just a lower bound behaves like .
  • Date and datetime columns expect the selector to return an ISO string, like"2024-03-15" or "2024-03-15T14:30".
datetime filtering assumes your cell values are local time. If they carry a Z or a UTC offset, the browser’s datetime-local input has no timezone to compare against and matches will be off. Supply a filterFunction for those columns.

Time-of-day filter

Log rows across several days. Open the Time filter, choose Between, and enter 02:00 and 04:00 to surface the nightly cron failures regardless of date. Try 22:00 to 06:00 for an overnight window that wraps past midnight.

Time
Service
Level
Message
08:12:04
auth
info
session started
23:41:19
billing
warn
retry scheduled
02:15:00
cron
error
nightly job failed
13:05:47
api
info
request handled
02:47:31
cron
error
nightly job failed
09:30:12
auth
info
session started
17:58:00
billing
warn
card declined
03:22:09
cron
error
nightly job failed
21:14:55
api
info
request handled
00:38:41
auth
warn
rate limited

Set filters: pick from a checklist

filterType: 'set' replaces the operator dropdown with a checklist of the column’s distinct values. Instead of typing an operator and a value, you check the values you want to keep. This is usually what people want for categorical columns (department, status, region, log level), where “Engineering or Design or Product” would otherwise need several OR conditions.

const columns: TableColumn<Row>[] = [
  { id: 'department', name: 'Department', selector: r => r.department, filterable: true, filterType: 'set' },
];

The checklist is built from your data, so there is nothing else to configure.

Set filter

Open the Service, Severity, or Owner filter and check the values to keep. Two incidents have no owner, so the Owner filter includes (Blanks). Use the search box to narrow a long list, then (Select all) to act on just the matches.

no set filter applied. Open a Service, Severity, or Owner filter
Service
Severity
Owner
Opened
auth
critical
Platform
2024-03-01
auth
major
Platform
2024-03-04
auth
minor
Platform
2024-03-08
billing
major
Payments
2024-03-02
billing
minor
Payments
2024-03-06
billing
critical
Payments
2024-03-11
cdn
critical
2024-03-05
cdn
major
2024-03-09
notifications
critical
Messaging
2024-03-10
notifications
minor
Messaging
2024-03-12
search
minor
Discovery
2024-03-02
search
major
Discovery
2024-03-07
  • Values are derived from the rows you pass in, sorted naturally. The list is the column’s full distinct set and does not change as you filter other columns. SetfilterOptions.values on the column to supply the list instead.
  • One cell can hold several values. A column of tags or a stack, formatted asReact, TypeScript, lists each part separately once you setfilterOptions.separator.
  • A search box narrows long lists, and (Select all) acts on what the search has narrowed to. Searching, unchecking (Select all), then checking one value is the quickest way to filter down to a single value.
  • Empty cells are collected under (Blanks), and are represented by the empty string in filter state.
  • Values are read through the column’s selector and compared as strings. formataffects only what the cell displays, not what the checklist shows.

Supplying the checklist values

Deriving values from the rows only works when the rows already contain every value worth filtering on. A column whose domain is fixed and known, a status or a priority, is usually the opposite case: the list is something you know up front, and the loaded rows are just a sample of it. SetfilterOptions.values on the column to supply the list yourself.

This matters most with server-side data. The table only holds the current page, so a status the server knows about but the page does not would never appear in the checklist, and the user could not ask for it. That is circular: the value only becomes selectable once rows carrying it are loaded, but loading them is the whole point of selecting it. Supplying the list breaks the loop.

The demo below holds only Active and Pending tickets, as though the first page had just come back from a server. All four statuses are still listed, and pickingClosed or Archived refetches.

Set filter with supplied values

Open the Status filter. All four statuses are offered even though only Active and Pending are loaded, so picking one the page does not hold can go and fetch it.

no filter applied. Open the Status filter: all four statuses are listed, though only Active and Pending are loaded
Subject
Status
Login loop on SSO
Active
Invoice PDF is blank
Active
Webhook retries stall
Pending
Export times out
Pending

Pair it with filterServer, which is implied by paginationServer, so the built-in matcher does not run a second time against rows the server already filtered. SeeServer-side filtering below.

  • Order is kept as given. Derived values are sorted naturally, but a supplied list is authored, so ['Low', 'Medium', 'High'] stays in that order rather than being alphabetized.
  • Blanks are only offered if you ask for one. Include an empty string in the list to get a(Blanks) entry. Derived lists add one whenever a row has an empty cell.
  • values also takes a function, receiving the rows the table is holding, so a fixed domain can be merged with whatever else turned up:{ values: rows => [...new Set([...STATUSES, ...rows.map(r => r.status)])] }.
  • The column still needs a selector. filterOptions.values supplies the checklist, but client-side matching reads cell values through the selector.

Cells holding several values

A column sometimes holds more than one thing per cell: a list of tags, a stack, the labels on an issue. Left alone, a set filter treats the whole cell as one value, so a row reading React, TypeScript offers exactly that string in the checklist and nothing for React on its own. SetfilterOptions.separator to split the cell into its parts.

Each part becomes its own checklist entry, and a row matches when any of its parts is selected. Checking React and Postgres shows every project using either, which is how a tag filter is normally read.

Set filter on a multi-value column

Open the Stack filter. Each technology is listed separately even though the cells hold comma separated strings, and picking one shows every project using it.

no filter applied. Open the Stack filter: each technology is listed on its own, not as "React, TypeScript"
Project
Stack
Checkout rewrite
React, TypeScript
Billing service
Go, Postgres
Design system
React, Storybook, TypeScript
Data pipeline
Python, Postgres
Marketing site
Astro, TypeScript
Internal scripts
  • Parts are trimmed, and empty ones dropped. 'React, , TypeScript,' yields two values, so ragged data does not litter the checklist with blanks.
  • A cell left with nothing counts as blank. An empty cell, or one holding only separators, still reaches the (Blanks) entry.
  • A RegExp works too, for data that is not consistently delimited:{ separator: /s*[,|]s*/ }.
  • Pair it with filterOptions.values to supply the parts yourself rather than deriving them from the loaded rows. The two are independent: values sets the checklist,separator governs how cells are matched against it.

Two conditions per column

Every filter popup has a + Add condition link. Adding a second condition reveals an AND / OR toggle: AND means a row has to match both, OR means either will do. That covers things like “starts with J but does not end with son” without writing a custom filter function. Set filters have no operators, so they show the checklist instead of this link.

Apply / Clear behavior

Filters apply only when the user clicks Apply. Typing does not immediately re-filter. This avoids jarring mid-keystroke changes on large datasets. Clicking Clear resets the column's filter and applies immediately.

Keyboard and accessibility

  • The filter button carries aria-haspopup="dialog" and aria-expanded, and the panel is a role="dialog" labelled for screen readers.
  • Opening the panel moves focus into it. Tab and Shift+Tab cycle within the panel rather than escaping behind it, and Escape closes it and returns focus to the filter button.
  • In a set filter, Tab moves through the search box, (Select all), each value, then Clear and Apply; Space toggles the focused checkbox. Checkboxes show a focus ring, and the focused row is highlighted.
  • While a search narrows the checklist, (Select all) acts only on the matches, and announces that to screen readers.
  • The panel flips above its button near the bottom edge, clamps within the viewport, and scrolls internally when it cannot fit. It fades in on open, and holds still under prefers-reduced-motion: reduce.

Filter state

You can ignore this section until you need to read or write filters yourself, which comes up when you persist them in a URL, restore them on load, or filter on the server. One FilterState describes one column’s filter. Operator-based types fill in condition1, plus condition2 andlogic when there is a second condition. Set filters use values instead and ignore the conditions entirely.

import type { FilterState } from 'react-data-table-component';

// Operator-based: "starts with J AND ends with son"
const nameFilter: FilterState = {
  condition1: { operator: 'startsWith', value: 'J' },
  condition2: { operator: 'endsWith', value: 'son' },
  logic: 'AND', // 'AND' | 'OR' — defaults to 'AND'
};

// Set filter: "keep Engineering and Design rows, plus rows with no department"
const deptFilter: FilterState = {
  condition1: { operator: 'equals' },   // ignored by set filters
  values: ['Engineering', 'Design', ''],
};

For a set filter, values: undefined means no selection has been made yet and matches every row; an empty array means nothing is selected and matches none. Cells that are empty or contain only whitespace are all treated as blanks and selected with the empty string, shown in the checklist as (Blanks).

Values that appear after a set filter is applied

When the built-in panel applies a set filter it also records knownValues, the values in the checklist at that moment. If new data later brings a value that was not on that list, it was never unchecked, so those rows stay visible. Checking or unchecking anything in the panel makes the selection explicit again and records a fresh snapshot.

Omit knownValues when building filter state yourself and values is treated as an exhaustive allow-list, so anything not listed is filtered out, including values added later.

const filter: FilterState = {
  condition1: { operator: 'equals' },
  values: ['Engineering'],
  // Design and Support were in the checklist and left unchecked; anything else is new
  knownValues: ['Engineering', 'Design', 'Support'],
};

Two helpers are exported for working with filter state: emptyFilterState builds a blank one for a given filter type, and isFilterActive tells you whether a filter is actually narrowing anything.

import { emptyFilterState, isFilterActive } from 'react-data-table-component';

// Create a default-empty FilterState for a given type
emptyFilterState('number'); // { condition1: { operator: 'equals' } }
emptyFilterState('text'); // { condition1: { operator: 'contains' } }

// Check whether a FilterState is actually filtering anything
isFilterActive({ condition1: { operator: 'contains' } }); // false — no value
isFilterActive({ condition1: { operator: 'contains', value: 'a' } }); // true
isFilterActive({ condition1: { operator: 'blank' } }); // true — no value needed

Custom filter function

Override built-in operator logic per column with filterFunction. It receives the full FilterState so both conditions are available:

import type { TableColumn, FilterState } from 'react-data-table-component';

const columns: TableColumn<Row>[] = [
  {
    id: 'tags',
    name: 'Tags',
    selector: r => r.tags.join(', '),
    filterable: true,
    filterFunction: (row, filter) => {
      const term = (filter.condition1.value ?? '').toLowerCase();
      return row.tags.some(tag => tag.toLowerCase().includes(term));
    },
  },
];

Controlled mode

Pass filterValues and onFilterChange to own the filter state yourself. Useful for persisting it in a URL or resetting it programmatically.onFilterChange fires on every Apply or Clear click.

import { useState } from 'react';
import DataTable, { type FilterState } from 'react-data-table-component';

function App() {
  const [filterValues, setFilterValues] = useState<Record<string | number, FilterState>>({});
  const [resetPage, setResetPage] = useState(false);

  function handleFilterChange(columnId: string | number, filter: FilterState) {
    setFilterValues(prev => ({ ...prev, [columnId]: filter }));
    setResetPage(v => !v); // jump back to page 1 after each filter
  }

  return (
    <DataTable
      columns={columns}
      data={data}
      filterValues={filterValues}
      onFilterChange={handleFilterChange}
      pagination
      paginationResetDefaultPage={resetPage}
    />
  );
}

Server-side filtering

If your backend does the filtering, add filterServer. The popups still render and still callonFilterChange, but the built-in matcher is skipped, so the rows your server returned are shown as-is. paginationServer turns this on for you, since a page cannot be filtered against rows it does not hold.

const [filterValues, setFilterValues] = useState({});

const handleFilterChange = (columnId, filter) => {
  const next = { ...filterValues, [columnId]: filter };
  setFilterValues(next);
  fetchFromApi({ filters: next, page: 1 }).then(setRows);
};

<DataTable
  columns={columns}
  data={rows}
  filterServer
  filterValues={filterValues}
  onFilterChange={handleFilterChange}
/>

Localization

Every string in the filter panel is overridable through the table-levellocalization prop, under its filter key. SeeLocalization for the full list of keys and their defaults.

Headless usage

Use useColumnFilter directly when building a custom table with the headless hooks. See Headless hooks for the full API.

import { useColumnFilter, type FilterState } from 'react-data-table-component';

const { filterValues, handleFilterChange, filteredData } = useColumnFilter(columns);

// Call handleFilterChange when the user applies a filter in your custom UI
function onApply(columnId: string | number, filter: FilterState) {
  handleFilterChange(columnId, filter);
}

// Apply all active filters before rendering rows
const rows = filteredData(tableRows);

See it combined with other features in the Server-side sort, page & filter recipe and URL-synced table state.

Prop reference

PropTypeDefaultDescription
filterValuesRecord<string | number, FilterState>-Controlled filter state. Omit to use internal state. See Filtering.
onFilterChange(columnId, filter: FilterState) => void-Called when the user clicks Apply or Clear in a filter popup.
filterServerbooleanfalseDisable client-side filtering. Use with onFilterChange to filter remotely.
column.filterOptions{ values?: string[] | ((rows: T[]) => string[]); separator?: string | RegExp }-Per-column set filter settings. values supplies the checklist, separator splits multi-value cells.

Per-column filtering is configured on each TableColumnvia filterable, filterType, and filterFunction.