Skip to content

Custom plugins

Extend Handsontable’s functionality by writing your custom plugin. Use the BasePlugin for a quick start.

Overview

Plugins are a great way of extending Handsontable’s capabilities. In fact, most Handsontable features are provided by plugins.

Reach for a custom plugin when you need to share state across multiple hooks. A plugin also fits when you need an enable, disable, or update lifecycle, or a reusable, registrable unit for other developers to use. If you only need to react to a single action, add a hook instead - see Events and hooks.

  1. Prerequisites

    Import the following:

    • BasePlugin - a built-in interface that lets you work within Handsontable’s lifecycle,
    • registerPlugin - a utility to register a plugin in the Handsontable plugin registry.

    In TypeScript, also import HotInstance, CellChange, and ChangeSource.

    JavaScript
    import { BasePlugin, registerPlugin } from 'handsontable/plugins';
    TypeScript
    import { BasePlugin, registerPlugin } from 'handsontable/plugins';
    import type { CellChange, ChangeSource, HotInstance } from 'handsontable';
  2. Extend the BasePlugin

    The best way to start creating your own plugin is to extend the BasePlugin.

    The BasePlugin interface takes care of:

    • Backward compatibility
    • Memory leak prevention
    • Properly binding your plugin’s instance to Handsontable

    Declare configuration on the class in the TypeScript example. disablePlugin() sets that field to null, so the afterChange log reads configuration?.msg.

    JavaScript
    export class CustomPlugin extends BasePlugin {
    /**
    * Define a unique key (a string) for your plugin.
    * The key becomes the plugin's alias.
    * Handsontable registers the plugin under that alias.
    * When an `updateSettings()` call includes the plugin's alias,
    * your plugin's state gets updated.
    * You can also use the alias to recognize the plugin's
    * options passed through the Setting object at Handsontable's initialization.
    *
    * @returns {string}
    */
    static get PLUGIN_KEY() {
    return 'customPlugin';
    }
    /**
    * Define additional setting keys (an array of strings) for your plugin.
    * When an `updateSettings()` call includes at least one of those setting keys,
    * your plugin's state gets updated.
    * If you set SETTING_KEYS() to return `true`, your plugin updates on every `updateSettings()` call.
    * If you set SETTING_KEYS() to return `false`, your plugin never updates on any `updateSettings()` call.
    *
    * @returns {Array|boolean}
    */
    static get SETTING_KEYS() {
    return true;
    }
    /**
    * Extend the default constructor and define internal properties for your plugin.
    *
    * @param {Handsontable} hotInstance
    */
    constructor(hotInstance) {
    /**
    * The [`BasePlugin`](/docs/javascript-data-grid/api/base-plugin/)'s constructor adds a `this.hot` property to your plugin.
    * The `this.hot` property:
    * - Is a reference to the Handsontable instance.
    * - Can't be overwritten.
    * - Gives you access to columns' and rows' index mappers.
    */
    super(hotInstance);
    // Initialize all your public properties in the class'
    // constructor.
    this.configuration = {
    enabled: false,
    msg: ''
    };
    }
    /**
    * Unifies configuration passed to settings object.
    *
    * @returns {object}
    * @throws {Error}
    */
    getUnifiedConfig() {
    const pluginSettings = this.hot.getSettings()[CustomPlugin.PLUGIN_KEY];
    if (pluginSettings === true) {
    return {
    enabled: true,
    msg: 'default msg boolean'
    };
    }
    if (Object.prototype.toString.call(pluginSettings) === '[object Object]') {
    return {
    enabled: true,
    msg: 'default msg obj',
    ...pluginSettings
    };
    }
    if (pluginSettings === false) {
    return {
    enabled: false,
    msg: ''
    };
    }
    throw new Error(
    `${CustomPlugin.PLUGIN_KEY} - incorrect plugins configuration.
    Passed:
    - type: ${typeof pluginSettings}
    - value: ${JSON.stringify(pluginSettings, null, ' ')}
    Expected:
    - boolean
    - object
    `
    );
    }
    /**
    * Checks if the plugin is enabled in the settings.
    */
    isEnabled() {
    const pluginSettings = this.getUnifiedConfig();
    return pluginSettings.enabled;
    }
    /**
    * The `enablePlugin` method is triggered on the `beforeInit` hook.
    * It should contain your plugin's initial setup and hook connections.
    * This method is run only if the `isEnabled` method returns `true`.
    */
    enablePlugin() {
    // Get the plugin's configuration from the initialization object.
    this.configuration = this.getUnifiedConfig();
    // Add all your plugin hooks here. It's a good idea to use arrow
    // functions to keep the plugin as a context.
    this.addHook('afterChange', (changes, source) => this.onAfterChange(changes, source));
    // The `super` method sets the `this.enabled` property to `true`.
    // It is a necessary step to update the plugin's settings
    // properly.
    super.enablePlugin();
    }
    /**
    * The `disablePlugin` method disables the plugin.
    */
    disablePlugin() {
    // Reset all of your plugin class properties to their default
    // values here.
    this.configuration = null;
    // The `BasePlugin.disablePlugin` method takes care of clearing
    // the hook connections
    // and assigning the 'false' value to the 'this.enabled' property.
    super.disablePlugin();
    }
    /**
    * The `updatePlugin` method is called on the `afterUpdateSettings` hook
    * (unless the `updateSettings` method turned the plugin off),
    * but only if the config object passed to the `updateSettings` method
    * contains entries relevant to that particular plugin.
    *
    * The `updatePlugin` method should contain anything that your plugin needs to do to work correctly
    * after updating the Handsontable instance settings.
    */
    updatePlugin() {
    // The `updatePlugin` method needs to contain all the code needed
    // to properly re-enable the plugin.
    // In most cases, disabling and enabling the plugin should
    // do the trick.
    const { enabled, msg } = this.getUnifiedConfig();
    // You can decide if updating the settings triggers the
    // disable->enable routine or not.
    if (enabled === false && this.enabled === true) {
    this.disablePlugin();
    } else if (enabled === true && this.enabled === false) {
    this.enablePlugin();
    }
    // If you need to update just a single option.
    if (this.configuration !== null && msg && this.configuration.msg !== msg) {
    this.configuration.msg = msg;
    }
    super.updatePlugin();
    }
    /**
    * Define your external methods.
    */
    externalMethodExample() {
    // Method definition.
    }
    /**
    * The afterChange hook callback.
    *
    * @param {CellChange[]} changes An array of changes.
    * @param {string} source Describes the source of the change.
    */
    onAfterChange(changes, source) {
    // afterChange callback goes here.
    console.log(
    `${CustomPlugin.PLUGIN_KEY}.onAfterChange - ${this.configuration.msg}`,
    changes,
    source
    );
    }
    /**
    * The `destroy` method is the best place to clean up any instances,
    * objects or index mappers created during the plugin's lifecycle.
    */
    destroy() {
    // The `super` method cleans up the plugin's event callbacks, hook
    // connections, and properties.
    super.destroy();
    }
    }
    TypeScript
    type CustomPluginConfig = {
    enabled: boolean;
    msg: string;
    };
    export class CustomPlugin extends BasePlugin {
    configuration: CustomPluginConfig | null;
    /**
    * Define a unique key (a string) for your plugin.
    * The key becomes the plugin's alias.
    * Handsontable registers the plugin under that alias.
    * When an `updateSettings()` call includes the plugin's alias,
    * your plugin's state gets updated.
    * You can also use the alias to recognize the plugin's
    * options passed through the Setting object at Handsontable's initialization.
    *
    * @returns The plugin alias.
    */
    static get PLUGIN_KEY() {
    return 'customPlugin';
    }
    /**
    * Define additional setting keys (an array of strings) for your plugin.
    * When an `updateSettings()` call includes at least one of those setting keys,
    * your plugin's state gets updated.
    * If you set SETTING_KEYS() to return `true`, your plugin updates on every `updateSettings()` call.
    * If you set SETTING_KEYS() to return `false`, your plugin never updates on any `updateSettings()` call.
    *
    * @returns The setting keys, or a boolean.
    */
    static get SETTING_KEYS() {
    return true;
    }
    /**
    * Extend the default constructor and define internal properties for your plugin.
    *
    * @param hotInstance The Handsontable instance.
    */
    constructor(hotInstance: HotInstance) {
    /**
    * The [`BasePlugin`](/docs/javascript-data-grid/api/base-plugin/)'s constructor adds a `this.hot` property to your plugin.
    * The `this.hot` property:
    * - Is a reference to the Handsontable instance.
    * - Can't be overwritten.
    * - Gives you access to columns' and rows' index mappers.
    */
    super(hotInstance);
    // Initialize all your public properties in the class'
    // constructor.
    this.configuration = {
    enabled: false,
    msg: ''
    };
    }
    /**
    * Unifies configuration passed to settings object.
    *
    * @returns The plugin options.
    * @throws {Error}
    */
    getUnifiedConfig(): CustomPluginConfig {
    const pluginSettings = this.hot.getSettings()[CustomPlugin.PLUGIN_KEY];
    if (pluginSettings === true) {
    return {
    enabled: true,
    msg: 'default msg boolean'
    };
    }
    if (Object.prototype.toString.call(pluginSettings) === '[object Object]') {
    return {
    enabled: true,
    msg: 'default msg obj',
    ...pluginSettings
    };
    }
    if (pluginSettings === false) {
    return {
    enabled: false,
    msg: ''
    };
    }
    throw new Error(
    `${CustomPlugin.PLUGIN_KEY} - incorrect plugins configuration.
    Passed:
    - type: ${typeof pluginSettings}
    - value: ${JSON.stringify(pluginSettings, null, ' ')}
    Expected:
    - boolean
    - object
    `
    );
    }
    /**
    * Checks if the plugin is enabled in the settings.
    */
    isEnabled() {
    const pluginSettings = this.getUnifiedConfig();
    return pluginSettings.enabled;
    }
    /**
    * The `enablePlugin` method is triggered on the `beforeInit` hook.
    * It should contain your plugin's initial setup and hook connections.
    * This method is run only if the `isEnabled` method returns `true`.
    */
    enablePlugin() {
    // Get the plugin's configuration from the initialization object.
    this.configuration = this.getUnifiedConfig();
    // Add all your plugin hooks here. It's a good idea to use arrow
    // functions to keep the plugin as a context.
    this.addHook('afterChange', (changes, source) => this.onAfterChange(changes, source));
    // The `super` method sets the `this.enabled` property to `true`.
    // It is a necessary step to update the plugin's settings
    // properly.
    super.enablePlugin();
    }
    /**
    * The `disablePlugin` method disables the plugin.
    */
    disablePlugin() {
    // Reset all of your plugin class properties to their default
    // values here.
    this.configuration = null;
    // The `BasePlugin.disablePlugin` method takes care of clearing
    // the hook connections
    // and assigning the 'false' value to the 'this.enabled' property.
    super.disablePlugin();
    }
    /**
    * The `updatePlugin` method is called on the `afterUpdateSettings` hook
    * (unless the `updateSettings` method turned the plugin off),
    * but only if the config object passed to the `updateSettings` method
    * contains entries relevant to that particular plugin.
    *
    * The `updatePlugin` method should contain anything that your plugin needs to do to work correctly
    * after updating the Handsontable instance settings.
    */
    updatePlugin() {
    // The `updatePlugin` method needs to contain all the code needed
    // to properly re-enable the plugin.
    // In most cases, disabling and enabling the plugin should
    // do the trick.
    const { enabled, msg } = this.getUnifiedConfig();
    // You can decide if updating the settings triggers the
    // disable->enable routine or not.
    if (enabled === false && this.enabled === true) {
    this.disablePlugin();
    } else if (enabled === true && this.enabled === false) {
    this.enablePlugin();
    }
    // If you need to update just a single option.
    if (this.configuration !== null && msg && this.configuration.msg !== msg) {
    this.configuration.msg = msg;
    }
    super.updatePlugin();
    }
    /**
    * Define your external methods.
    */
    externalMethodExample() {
    // Method definition.
    }
    /**
    * The afterChange hook callback.
    *
    * @param changes An array of changes, or `null`.
    * @param source Describes the source of the change.
    */
    onAfterChange(changes: CellChange[] | null, source: ChangeSource): void {
    // afterChange callback goes here.
    console.log(
    `${CustomPlugin.PLUGIN_KEY}.onAfterChange - ${this.configuration?.msg}`,
    changes,
    source
    );
    }
    /**
    * The `destroy` method is the best place to clean up any instances,
    * objects or index mappers created during the plugin's lifecycle.
    */
    destroy() {
    // The `super` method cleans up the plugin's event callbacks, hook
    // connections, and properties.
    super.destroy();
    }
    }
  3. Register CustomPlugin

    Now, register your plugin.

    There are two ways to register a plugin:

    • Option 1: Define a static getter named PLUGIN_KEY that the registerPlugin utility uses as the plugin’s alias. Check the example in the code snippet above.
      // You need to register your plugin in order to use it within
      // Handsontable.
      registerPlugin(CustomPlugin);
    • Option 2: Use a custom alias. Put a string in the first argument. The registerer uses that string as an alias, instead of the PLUGIN_KEY getter from CustomPlugin.
      registerPlugin('CustomAlias', CustomPlugin);
  4. Use your plugin in Handsontable

    To control the plugin’s options, pass a boolean or an object at the plugin’s initialization:

    import Handsontable from 'handsontable';
    import { CustomPlugin } from './customPlugin';
    const hotInstance = new Handsontable(container, {
    // Pass `true` to enable the plugin with default options.
    [CustomPlugin.PLUGIN_KEY]: true,
    // You can also enable the plugin by passing an object with options.
    [CustomPlugin.PLUGIN_KEY]: {
    msg: 'user-defined message',
    },
    // You can also initialize the plugin without enabling it at the
    // beginning.
    [CustomPlugin.PLUGIN_KEY]: false,
    });
  5. Get a reference to the plugin’s instance

    To use the plugin’s API, call the getPlugin method to get a reference to the plugin’s instance.

    getPlugin() returns BasePlugin, so cast the result to your plugin class to call its methods.

    JavaScript
    const pluginInstance = hotInstance.getPlugin(CustomPlugin.PLUGIN_KEY);
    pluginInstance.externalMethodExample();
    TypeScript
    const pluginInstance = hotInstance.getPlugin(CustomPlugin.PLUGIN_KEY) as CustomPlugin;
    pluginInstance.externalMethodExample();

APIs:

Core methods:

Hooks: