Files
dc/.agent/skills/hyperformula/references/custom-functions.md
T
dcbot 078da3d947
Build / Build-and-ng-test (pull_request) Successful in 5m52s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m1s
Build / Build-and-test-development (pull_request) Successful in 16m6s
chore(ai): moving .claude to .agent
2026-07-17 22:30:05 +01:00

4.2 KiB

Custom Functions

Extend HyperFormula with your own functions via FunctionPlugin. Authoritative doc:

Minimal plugin

Register before creating any instance — functions registered after buildFromArray / buildFromSheets / buildEmpty won't be available.

import { HyperFormula, FunctionPlugin, FunctionArgumentType } from 'hyperformula';

class MyPlugin extends FunctionPlugin {
  greet(ast, state) {
    return this.runFunction(
      ast.args,
      state,
      this.metadata('GREET'),
      (name) => `Hello, ${name}!`
    );
  }
}

MyPlugin.implementedFunctions = {
  GREET: {
    method: 'greet',
    parameters: [{ argumentType: FunctionArgumentType.STRING }],
  },
};

const translations = { enGB: { GREET: 'GREET' }, enUS: { GREET: 'GREET' } };

// PITFALL: registration MUST happen before any instance is built.
HyperFormula.registerFunctionPlugin(MyPlugin, translations);

const hf = HyperFormula.buildFromArray(
  [['World', '=GREET(A1)']],
  { licenseKey: 'gpl-v3' }
);
console.log(hf.getCellValue({ sheet: 0, col: 1, row: 0 })); // "Hello, World!"

Volatile functions

Mark a function as volatile so it recalculates on every change (like RAND or NOW). Volatile functions are expensive in large sheets — use sparingly.

MyPlugin.implementedFunctions = {
  RAND_ID: {
    method: 'randId',
    parameters: [],
    isVolatile: true,
  },
};

Argument types

import { FunctionArgumentType } from 'hyperformula';

// FunctionArgumentType.NUMBER
// FunctionArgumentType.STRING
// FunctionArgumentType.BOOLEAN
// FunctionArgumentType.NOERROR
// FunctionArgumentType.SCALAR
// FunctionArgumentType.RANGE       // accepts a cell range
// FunctionArgumentType.ANY

Use optionalArg: true, defaultValue, and minValue / maxValue to validate arguments declaratively:

MyPlugin.implementedFunctions = {
  CLAMP: {
    method: 'clamp',
    parameters: [
      { argumentType: FunctionArgumentType.NUMBER },
      { argumentType: FunctionArgumentType.NUMBER, optionalArg: true, defaultValue: 0 },
      { argumentType: FunctionArgumentType.NUMBER, optionalArg: true, defaultValue: 100 },
    ],
  },
};

Range arguments

Use RANGE argument type when the function takes a cell area. The callback receives the range as a 2D array.

class SumPositivesPlugin extends FunctionPlugin {
  sumPositives(ast, state) {
    return this.runFunction(
      ast.args,
      state,
      this.metadata('SUM_POSITIVES'),
      (range) => {
        let total = 0;
        for (const row of range.data) {
          for (const v of row) if (typeof v === 'number' && v > 0) total += v;
        }
        return total;
      }
    );
  }
}

SumPositivesPlugin.implementedFunctions = {
  SUM_POSITIVES: {
    method: 'sumPositives',
    parameters: [{ argumentType: FunctionArgumentType.RANGE }],
  },
};

Returning arrays

Custom functions can return 2D arrays. PITFALL: result arrays don't auto-resize when upstream dependencies change — the footprint is fixed at first evaluation.

class SplitPlugin extends FunctionPlugin {
  splitToRow(ast, state) {
    return this.runFunction(
      ast.args,
      state,
      this.metadata('SPLIT_ROW'),
      (text, sep) => [text.split(sep)]  // 2D array: one row, N cols
    );
  }
}

Error handling

Return a CellError to signal a formula error:

import { CellError, ErrorType } from 'hyperformula';

class DividePlugin extends FunctionPlugin {
  safeDivide(ast, state) {
    return this.runFunction(
      ast.args,
      state,
      this.metadata('SAFE_DIVIDE'),
      (a, b) => (b === 0 ? new CellError(ErrorType.DIV_BY_ZERO) : a / b)
    );
  }
}

Aliases and localized names

Expose one method under multiple function names, or localize:

MyPlugin.aliases = {
  HELLO: 'GREET', // calling =HELLO(x) runs the greet() method
};

const translations = {
  enGB: { GREET: 'GREET' },
  plPL: { GREET: 'POWITAJ' }, // =POWITAJ("Świat") in Polish
};
HyperFormula.registerFunctionPlugin(MyPlugin, translations);

Unregister / introspect

HyperFormula.unregisterFunctionPlugin(MyPlugin);
HyperFormula.unregisterFunction('GREET');
HyperFormula.getRegisteredFunctionNames('enGB');