hexojs/hexo · error · TypeError

fn must be a function

Error message

fn must be a function

What it means

Thrown by Console.register in the branch where three or four arguments were given, 'options' is truthy, but neither 'options' nor a later argument is a function. Hexo resolves the overloaded signatures (name,desc,fn | name,options,fn | name,desc,options,fn); when the expected fn slot is occupied by a non-function (e.g. an options object) and no real fn follows, the callback cannot be located.

Source

Thrown at lib/extend/console.ts:90

  register(name: string, desc: string, fn: AnyFn): void
  register(name: string, options: Option, fn: AnyFn): void
  register(name: string, desc: string, options: Option, fn: AnyFn): void
  register(name: string, desc: string | Option | AnyFn, options?: Option | AnyFn, fn?: AnyFn): void {
    if (!name) throw new TypeError('name is required');

    if (!fn) {
      if (options) {
        if (typeof options === 'function') {
          fn = options;

          if (typeof desc === 'object') { // name, options, fn
            options = desc;
            desc = '';
          } else { // name, desc, fn
            options = {};
          }
        } else {
          throw new TypeError('fn must be a function');
        }
      } else {
        // name, fn
        if (typeof desc === 'function') {
          fn = desc;
          options = {};
          desc = '';
        } else {
          throw new TypeError('fn must be a function');
        }
      }
    }

    if (fn.length > 1) {
      fn = Promise.promisify(fn);
    } else {
      fn = Promise.method(fn);
    }

View on GitHub (pinned to 059cb17494)

Solutions

  1. Ensure the function is the LAST argument in the (name, desc, options, fn) overload.
  2. If you only need name+options+fn, put options third and fn fourth.
  3. Double-check the overload you intend against the signature order.

Example fix

// before
hexo.extend.console.register('cmd', 'desc', { alias: 'c' });
// after
hexo.extend.console.register('cmd', 'desc', { alias: 'c' }, (args) => { /* ... */ });
Defensive patterns

Strategy: type-guard

Validate before calling

const args = [name, desc, options, fn].filter(a => a !== undefined);
if (typeof args[args.length - 1] !== 'function') throw new Error('last argument must be the handler');
hexo.extend.console.register(name, desc, options, fn);

Type guard

const isFn = (x: unknown): x is (...a: any[]) => any => typeof x === 'function';

Prevention

When it happens

Trigger: Calling register('name', 'desc', {}) (an options object but no fn), or register('name', { option: 1 }) where the second slot is an object and no function follows.

Common situations: A plugin author intended the (name, desc, fn) form but passed an options object as the third argument and omitted the function, or swapped the options and fn order.

Related errors


AI-assisted analysis of hexojs/hexo@059cb17494 (2026-08-12). Data as JSON: /api/errors/b969825b5405d268. Report an issue: GitHub.