Searching values
Enable the Search plugin and call query() on each keystroke to highlight matching cells across the grid.
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.
Simplest use case
The example below:
- Enables the
Searchplugin by settingsearchtotrue - Listens for
keyupevents on a search input - Calls
query()on each keystroke and re-renders the grid to apply highlighting
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example1');const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field');
// add a search input listenersearchField.addEventListener('keyup', (event) => { // get the `Search` plugin's instance const search = hot.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example1')!;
const hot = new Handsontable(container, { data, colHeaders: true, // enable the `Search` plugin search: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const searchField = document.querySelector('#search_field')!;
// add a search input listenersearchField.addEventListener('keyup', (event) => { // get the `Search` plugin's instance const search: Search = hot.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult);
hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field" type="search" placeholder="Search"> </div></div><div id="example1"></div>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
Searchplugin with a configuration object - Sets
searchResultClassto'my-class'
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example2');const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field2');
searchField.addEventListener('keyup', (event) => { const search = hot.getPlugin('search'); const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'],];
const container = document.querySelector('#example2')!;
const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field2')!;
searchField.addEventListener('keyup', (event) => { const search: Search = hot.getPlugin('search'); const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult); hot.render();});.ht-theme-main .handsontable .my-class { background: pink; color: red;}<div class="example-controls-container"> <div class="controls"> <input id="search_field2" type="search" placeholder="Search"> </div></div><div id="example2"></div>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
onlyExactMatchthat uses strict equality (===) - Enables the
Searchplugin with a configuration object - Sets
queryMethodtoonlyExactMatch
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom query methodfunction onlyExactMatch(queryStr, value) { return queryStr.toString() === value.toString();}
const container = document.querySelector('#example3');const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field3');
searchField.addEventListener('keyup', (event) => { const search = hot.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search.query(event.target.value);
console.log(queryResult); hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom query methodfunction onlyExactMatch(queryStr, value) { return queryStr.toString() === value.toString();}
const container = document.querySelector('#example3')!;
const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field3')!;
searchField.addEventListener('keyup', (event) => { const search: Search = hot.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult);
hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field3" type="search" placeholder="Search"> </div></div><div id="example3"></div>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
searchResultCounterthat counts matches and calls the default callback to preserve highlighting - Enables the
Searchplugin with a configuration object - Sets
callbacktosearchResultCounter
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
let searchResultCount = 0;const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom callback functionfunction searchResultCounter(instance, row, column, value, result) { const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, [instance, row, column, value, result]);
if (result) { searchResultCount++; }}
const container = document.querySelector('#example4');const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field4');const output = document.querySelector('#output');
searchField.addEventListener('keyup', (event) => { searchResultCount = 0;
const search = hot.getPlugin('search'); const queryResult = search.query(event.target.value);
console.log(queryResult); output.innerText = `${searchResultCount} results`; hot.render();});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { Search } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
let searchResultCount = 0;
const data: (string | number)[][] = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'],];
// define your custom callback functionfunction searchResultCounter( instance: Handsontable.Core, row: number, column: number, value: Handsontable.CellValue, result: boolean): void { const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, [instance, row, column, value, result]);
if (result) { searchResultCount++; }}
const container = document.querySelector('#example4')!;
const hot = new Handsontable(container, { 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',});
const searchField = document.querySelector('#search_field4')!;const output = document.querySelector('#output')!;
searchField.addEventListener('keyup', (event) => { searchResultCount = 0;
const search: Search = hot.getPlugin('search'); const queryResult = search.query((event.target as HTMLInputElement).value);
console.log(queryResult); (output as HTMLElement).innerText = `${searchResultCount} results`; hot.render();});<div class="example-controls-container"> <div class="controls"> <input id="search_field4" type="search" placeholder="Search"> </div> <output class="console" id="output">0 results</output></div><div id="example4"></div>Result
After following these steps, typing in your search input highlights matching cells with the htSearchResult CSS class. The query() call returns an array of matching { row, col, data } objects that you can use to build a results counter or navigate between matches.
Related API
How query() works
Calling query(queryStr, [callback], [queryMethod]) does two things:
- Iterates over every cell in the grid and tests each one using the
queryMethod. - After each test, calls the
callbackto update cell metadata (isSearchResult).
It returns an array of result objects - one for each matching cell:
| Property | Type | Description |
|---|---|---|
row | number | Visual row index of the matching cell |
col | number | Visual column index of the matching cell |
data | string|number|null | Value 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')
queryMethod signature
The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.
function queryMethod(query, value, cellProperties) { // return true for a match, false otherwise}| Parameter | Type | Description |
|---|---|---|
query | string | The search string passed to query() |
value | string|number|null | The cell value (from getDataAtCell()) |
cellProperties | object | The 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)
callback signature
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.
function callback(instance, row, col, data, testResult) { // update cell metadata based on testResult}| Parameter | Type | Description |
|---|---|---|
instance | Handsontable | The Handsontable instance |
row | number | Visual row index |
col | number | Visual column index |
data | string|number|null | The cell value |
testResult | boolean | true 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.
Plugin configuration options
Configuration options
Plugins