Skip to content

Searching values

Search data across Handsontable using the built-in API methods of the Search plugin, and implement your own search UI.

Overview

The Search plugin lets you scan all cells in the grid and get back a list of matches. Enable it by setting the search option to true or to a configuration object.

Once enabled, the plugin exposes the query(queryStr) method. Call it with a search string whenever the user types. By default, the search is case-insensitive and matches partial cell values.

How query() works

Calling query(queryStr, [callback], [queryMethod]) does two things:

  1. Iterates over every cell in the grid and tests each one using the queryMethod.
  2. After each test, calls the callback to update cell metadata (isSearchResult).

It returns an array of result objects - one for each matching cell:

PropertyTypeDescription
rownumberVisual row index of the matching cell
colnumberVisual column index of the matching cell
datastring|number|nullValue of the matching cell

After calling query(), call hot.render() to refresh visual highlighting.

Search result class

After query() runs, every cell where isSearchResult === true automatically receives the CSS class htSearchResult. You can replace this class in two ways:

  • At initialization: set search: { searchResultClass: 'my-class' }
  • Programmatically: call hot.getPlugin('search').setSearchResultClass('my-class')

Custom queryMethod

The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.

Signature:

function queryMethod(query, value, cellProperties) {
// return true for a match, false otherwise
}
ParameterTypeDescription
querystringThe search string passed to query()
valuestring|number|nullThe cell value (from getDataAtCell())
cellPropertiesobjectThe cell’s metadata object (includes locale, type, and other cell options)

The built-in default performs a case-insensitive, locale-aware substring match:

function defaultQueryMethod(query, value, cellProperties) {
if (query === undefined || query === null || query.length === 0) {
return false;
}
if (value === undefined || value === null) {
return false;
}
return value.toString().toLocaleLowerCase(cellProperties.locale)
.indexOf(query.toLocaleLowerCase(cellProperties.locale)) !== -1;
}

You can set a custom query method in three ways:

  • At initialization: search: { queryMethod: myQueryMethod }
  • Programmatically: hot.getPlugin('search').setQueryMethod(myQueryMethod)
  • Per query() call: searchPlugin.query(queryStr, callback, myQueryMethod) (applies to that call only)

Custom result callback

The callback function is called for every cell during a query() run, whether or not the cell matches. It is responsible for updating cell metadata so the renderer knows which cells to highlight.

Signature:

function callback(instance, row, col, data, testResult) {
// update cell metadata based on testResult
}
ParameterTypeDescription
instanceHandsontableThe Handsontable instance
rownumberVisual row index
colnumberVisual column index
datastring|number|nullThe cell value
testResultbooleantrue if the cell matches the query, false otherwise

The built-in default sets the isSearchResult flag on each cell’s metadata:

function defaultCallback(instance, row, col, data, testResult) {
instance.getCellMeta(row, col).isSearchResult = testResult;
}

If you override the callback to add custom logic (for example, to count results), call the default behavior manually so that cell highlighting still works.

You can set a custom callback in three ways:

  • At initialization: search: { callback: myCallback }
  • Programmatically: hot.getPlugin('search').setCallback(myCallback)
  • Per query() call: searchPlugin.query(queryStr, myCallback) (applies to that call only)

Per-cell queryMethod and callback

Both queryMethod and callback can be overridden for individual cells, columns, or rows using Handsontable’s cascading configuration model. Set a search object directly in a cell, columns, or rows entry:

handsontable({
data: myData,
search: true,
columns: [
{},
// Column 1: exact match only, everything else uses the global queryMethod
{
search: {
queryMethod(queryStr, value) {
return queryStr.toString() === value.toString();
}
}
}
]
});

You can also set it programmatically on a specific cell using setCellMeta():

hot.setCellMeta(row, col, 'search', {
queryMethod(queryStr, value) {
return queryStr.toString() === value.toString();
}
});

Per-cell settings take precedence over the plugin-level queryMethod and callback. Only queryMethod and callback support per-cell overrides - searchResultClass does not.

Simplest use case

The example below:

  • Enables the Search plugin by setting search to true
  • Listens for keyup events on a search input
  • Calls query() on each keystroke and re-renders the grid to apply highlighting
JavaScript
import { useRef, useCallback } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event) => {
const hot = hotRef.current?.hotInstance;
// get the `Search` plugin's instance
const search = hot?.getPlugin('search');
// use the `Search` plugin's `query()` method
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field"
type="search"
placeholder="Search"
onKeyUp={(event) => searchFieldKeyupCallback(event)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
search={true}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useCallback } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const hot = hotRef.current?.hotInstance;
// get the `Search` plugin's instance
const search = hot?.getPlugin('search');
// use the `Search` plugin's `query()` method
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field"
type="search"
placeholder="Search"
onKeyUp={(event) => searchFieldKeyupCallback(event)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
search={true}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;

Custom search result class

You can style search results with a custom CSS class, using the Search plugin’s searchResultClass option.

The example below highlights search results with a pink background and red text. To do this, it:

  • Defines a custom CSS class called my-class, scoped to .ht-theme-main .handsontable
  • Enables the Search plugin with a configuration object
  • Sets searchResultClass to 'my-class'
JavaScript
import { useRef, useCallback } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event) => {
const hot = hotRef.current?.hotInstance;
const search = hot?.getPlugin('search');
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field2"
type="search"
placeholder="Search"
onKeyUp={(...args) => searchFieldKeyupCallback(...args)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom CSS class
searchResultClass: 'my-class',
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useCallback } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const hot = hotRef.current?.hotInstance;
const search = hot?.getPlugin('search');
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field2"
type="search"
placeholder="Search"
onKeyUp={(...args) => searchFieldKeyupCallback(...args)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom CSS class
searchResultClass: 'my-class',
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
CSS
.ht-theme-main .handsontable .my-class {
background: pink;
color: red;
}

Custom query method

You can replace the built-in substring search with a custom query method, using the queryMethod option.

The example below searches only for exact matches. To do this, it:

  • Defines a custom query method called onlyExactMatch that uses strict equality (===)
  • Enables the Search plugin with a configuration object
  • Sets queryMethod to onlyExactMatch
JavaScript
import { useRef, useCallback } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'white', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event) => {
const hot = hotRef.current?.hotInstance;
const search = hot?.getPlugin('search');
// use the `Search`'s `query()` method
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
// define your custom query method
function onlyExactMatch(queryStr, value) {
return queryStr.toString() === value.toString();
}
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field3"
type="search"
placeholder="Search"
onKeyUp={(...args) => searchFieldKeyupCallback(...args)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom query method
queryMethod: onlyExactMatch,
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useCallback } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'white', 'gray'],
];
const searchFieldKeyupCallback = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const hot = hotRef.current?.hotInstance;
const search = hot?.getPlugin('search');
// use the `Search`'s `query()` method
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render();
},
[hotRef.current]
);
// define your custom query method
function onlyExactMatch(queryStr: { toString: () => any }, value: { toString: () => any }) {
return queryStr.toString() === value.toString();
}
return (
<>
<div className="example-controls-container">
<div className="controls">
<input
id="search_field3"
type="search"
placeholder="Search"
onKeyUp={(...args) => searchFieldKeyupCallback(...args)}
/>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom query method
queryMethod: onlyExactMatch,
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;

Custom callback

You can add a custom callback function, using the Search plugin’s callback option.

The example below displays the number of matching search results. To do this, it:

  • Defines a custom callback function called searchResultCounter that counts matches and calls the default callback to preserve highlighting
  • Enables the Search plugin with a configuration object
  • Sets callback to searchResultCounter
JavaScript
import { useRef, useState } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hot4Ref = useRef(null);
const [resultCount, setResultCounter] = useState(0);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'white', 'gray'],
];
// define your custom callback function
function searchResultCounter(_instance, _row, _col, _value, result) {
const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) {
instance.getCellMeta(row, col).isSearchResult = testResult;
};
DEFAULT_CALLBACK.apply(this, arguments);
if (result) {
setResultCounter((count) => count + 1);
}
}
const handleKeyUp = (event) => {
setResultCounter(0);
const search = hot4Ref.current?.hotInstance?.getPlugin('search');
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot4Ref.current?.hotInstance?.render();
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<input id="search_field4" type="search" placeholder="Search" onKeyUp={handleKeyUp} />
</div>
<output className="console" id="output">
{resultCount} results
</output>
</div>
<HotTable
ref={hot4Ref}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom callback function
callback: searchResultCounter,
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useState } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import Handsontable from 'handsontable/base';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hot4Ref = useRef<HotTableRef>(null);
const [resultCount, setResultCounter] = useState(0);
const data = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'white', 'gray'],
];
// define your custom callback function
function searchResultCounter(
this: Handsontable,
_instance: Handsontable,
_row: number,
_col: number,
_value: any,
result: any
) {
const DEFAULT_CALLBACK = function (instance: Handsontable, row: number, col: number, _data: any, testResult: any) {
instance.getCellMeta(row, col).isSearchResult = testResult;
};
DEFAULT_CALLBACK.apply(this, arguments as any);
if (result) {
setResultCounter((count) => count + 1);
}
}
const handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
setResultCounter(0);
const search = hot4Ref.current?.hotInstance?.getPlugin('search');
const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot4Ref.current?.hotInstance?.render();
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<input id="search_field4" type="search" placeholder="Search" onKeyUp={handleKeyUp} />
</div>
<output className="console" id="output">
{resultCount} results
</output>
</div>
<HotTable
ref={hot4Ref}
data={data}
colHeaders={true}
// enable the `Search` plugin
search={{
// add your custom callback function
callback: searchResultCounter,
}}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;

Configuration options

Plugins