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
TypeScript
/* file: app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';
@Component({
selector: 'example1-searching-values',
standalone: false,
template: ` <div class="example-controls-container">
<div class="controls">
<input
id="search_field"
type="search"
placeholder="Search"
(keyup)="searchFieldKeyup($event)"
/>
</div>
</div>
<div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class Example1SearchingValuesComponent {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
readonly gridSettings: GridSettings = {
colHeaders: true,
search: true,
height: 'auto',
autoWrapRow: true,
autoWrapCol: true
};
searchFieldKeyup(event: KeyboardEvent): void {
const hot = this.hotTable?.hotInstance;
// get the `Search` plugin's instance
const search = hot?.getPlugin('search');
// use the `Search` plugin's `query()` method
const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render();
}
}
/* end-file */
/* file: app.module.ts */
import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */
import { Example1SearchingValuesComponent } from './app.component';
/* end:skip-in-compilation */
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HOT_GLOBAL_CONFIG,
useValue: {
license: NON_COMMERCIAL_LICENSE,
} as HotGlobalConfig
}
],
};
@NgModule({
imports: [ BrowserModule, HotTableModule, CommonModule ],
declarations: [ Example1SearchingValuesComponent ],
providers: [...appConfig.providers],
bootstrap: [ Example1SearchingValuesComponent ]
})
export class AppModule { }
/* end-file */
HTML
<div>
<example1-searching-values></example1-searching-values>
</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 Search plugin with a configuration object
  • Sets searchResultClass to 'my-class'
TypeScript
/* file: app.component.ts */
import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';
@Component({
selector: 'example2-searching-values',
standalone: false,
template: ` <div class="example-controls-container">
<div class="controls">
<input
id="search_field2"
type="search"
placeholder="Search"
(keyup)="searchFieldKeyup($event)"
/>
</div>
</div>
<div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
styles: `.ht-theme-main .handsontable .my-class {
background: pink;
color: red;
}`,
encapsulation: ViewEncapsulation.None,
})
export class Example2SearchingValuesComponent {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
readonly gridSettings: GridSettings = {
colHeaders: true,
search: {
searchResultClass: 'my-class',
},
height: 'auto',
autoWrapRow: true,
autoWrapCol: true
};
searchFieldKeyup(event: KeyboardEvent): void {
const hot = this.hotTable?.hotInstance;
// get the `Search` plugin's instance
const search = hot?.getPlugin('search');
// use the `Search` plugin's `query()` method
const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render();
}
}
/* end-file */
/* file: app.module.ts */
import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */
import { Example2SearchingValuesComponent } from './app.component';
/* end:skip-in-compilation */
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HOT_GLOBAL_CONFIG,
useValue: {
license: NON_COMMERCIAL_LICENSE,
} as HotGlobalConfig
}
],
};
@NgModule({
imports: [ BrowserModule, HotTableModule, CommonModule ],
declarations: [ Example2SearchingValuesComponent ],
providers: [...appConfig.providers],
bootstrap: [ Example2SearchingValuesComponent ]
})
export class AppModule { }
/* end-file */
HTML
<div>
<example2-searching-values></example2-searching-values>
</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 onlyExactMatch that uses strict equality (===)
  • Enables the Search plugin with a configuration object
  • Sets queryMethod to onlyExactMatch
TypeScript
/* file: app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';
@Component({
selector: 'example3-searching-values',
standalone: false,
template: ` <div class="example-controls-container">
<div class="controls">
<input
id="search_field3"
type="search"
placeholder="Search"
(keyup)="searchFieldKeyup($event)"
/>
</div>
</div>
<div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class Example3SearchingValuesComponent {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
readonly gridSettings: GridSettings = {
colHeaders: true,
search: {
// add your custom query method
queryMethod: this.onlyExactMatch,
},
height: 'auto',
autoWrapRow: true,
autoWrapCol: true
};
searchFieldKeyup(event: KeyboardEvent): void {
const hot = this.hotTable?.hotInstance;
// get the `Search` plugin's instance
const search = hot?.getPlugin('search');
// use the `Search` plugin's `query()` method
const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render();
}
// define your custom query method
onlyExactMatch(
queryStr: { toString: () => any },
value: { toString: () => any }
): boolean {
return queryStr.toString() === value.toString();
}
}
/* end-file */
/* file: app.module.ts */
import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */
import { Example3SearchingValuesComponent } from './app.component';
/* end:skip-in-compilation */
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HOT_GLOBAL_CONFIG,
useValue: {
license: NON_COMMERCIAL_LICENSE,
} as HotGlobalConfig
}
],
};
@NgModule({
imports: [ BrowserModule, HotTableModule, CommonModule ],
declarations: [ Example3SearchingValuesComponent ],
providers: [...appConfig.providers],
bootstrap: [ Example3SearchingValuesComponent ]
})
export class AppModule { }
/* end-file */
HTML
<div>
<example3-searching-values></example3-searching-values>
</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 searchResultCounter that counts matches and calls the default callback to preserve highlighting
  • Enables the Search plugin with a configuration object
  • Sets callback to searchResultCounter
TypeScript
/* file: app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';
import Handsontable from 'handsontable/base';
@Component({
selector: 'example4-searching-values',
standalone: false,
template: ` <div class="example-controls-container">
<div class="controls">
<input
id="search_field3"
type="search"
placeholder="Search"
(keyup)="searchFieldKeyup($event)"
/>
</div>
<output class="console" id="output"> {{ resultCount }} results </output>
</div>
<div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>`,
})
export class Example4SearchingValuesComponent {
@ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [
['Tesla', 2017, 'black', 'black'],
['Nissan', 2018, 'blue', 'blue'],
['Chrysler', 2019, 'yellow', 'black'],
['Volvo', 2020, 'yellow', 'gray'],
];
readonly gridSettings: GridSettings = {
colHeaders: true,
search: {
// add your custom callback function
callback: (
_instance: Handsontable,
_row: number,
_col: number,
_value: any,
result: any
) => this.searchResultCounter(_instance, _row, _col, _value, result),
},
height: 'auto',
autoWrapRow: true,
autoWrapCol: true
};
resultCount = 0;
searchFieldKeyup(event: KeyboardEvent): void {
this.setResultCounter(0);
const search = this.hotTable?.hotInstance?.getPlugin('search');
const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
this.hotTable?.hotInstance?.render();
}
// define your custom callback function
searchResultCounter(
_instance: Handsontable,
_row: number,
_col: number,
_value: any,
result: any
): void {
_instance.getCellMeta(_row, _col).isSearchResult = result;
if (result) {
this.setResultCounter(this.resultCount + 1);
}
}
private setResultCounter(count: number): void {
this.resultCount = count;
}
}
/* end-file */
/* file: app.module.ts */
import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */
import { Example4SearchingValuesComponent } from './app.component';
/* end:skip-in-compilation */
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HOT_GLOBAL_CONFIG,
useValue: {
license: NON_COMMERCIAL_LICENSE,
} as HotGlobalConfig
}
],
};
@NgModule({
imports: [ BrowserModule, HotTableModule, CommonModule ],
declarations: [ Example4SearchingValuesComponent ],
providers: [...appConfig.providers],
bootstrap: [ Example4SearchingValuesComponent ]
})
export class AppModule { }
/* end-file */
HTML
<div>
<example4-searching-values></example4-searching-values>
</div>

Configuration options

Plugins