hexojs/hexo · error · TypeError

fn must be a function

Error message

fn must be a function

What it means

Thrown by Filter.register when, after argument shifting, 'fn' is not a function. Hexo filters support (fn,priority), (type,fn), and (type,fn,priority); when the first arg is a function it is promoted to fn. If after that resolution fn is still not callable, the registration is invalid.

Source

Thrown at lib/extend/filter.ts:50

  list(type?: string) {
    if (!type) return this.store;
    return this.store[type] || [];
  }

  register(fn: StoreFunction): void
  register(fn: StoreFunction, priority: number): void
  register(type: string, fn: StoreFunction): void
  register(type: string, fn: StoreFunction, priority: number): void
  register(type: string | StoreFunction, fn?: StoreFunction | number, priority?: number): void {
    if (!priority) {
      if (typeof type === 'function') {
        priority = fn as number;
        fn = type;
        type = 'after_post_render';
      }
    }

    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    type = typeAlias[type as string] || type;
    priority = priority == null ? 10 : priority;

    const store = this.store[type as string] || [];
    this.store[type as string] = store;

    fn.priority = priority;
    store.push(fn);

    store.sort((a, b) => a.priority - b.priority);
  }

  unregister(type: string, fn: StoreFunction): void {
    if (!type) throw new TypeError('type is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    type = typeAlias[type] || type;

View on GitHub (pinned to 059cb17494)

Solutions

  1. Pass a function as the filter callback in the correct positional slot.
  2. Verify imported filter symbols are defined before registering.
  3. Match the overload: register(type, fn) or register(type, fn, priority).

Example fix

// before
hexo.extend.filter.register('before_post_render', filterDef);
// after
hexo.extend.filter.register('before_post_render', (data) => { /* ... */ return data; });
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== 'function') throw new Error('filter callback must be a function');
hexo.extend.filter.register(type, fn, priority);

Type guard

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

Prevention

When it happens

Trigger: Calling hexo.extend.filter.register('before_post_render', 'notafn'), register('type', undefined), or register('type', 123) where the second argument is not a function.

Common situations: A plugin passes a config string/object instead of a filter callback, or imports a filter that is undefined due to a bad import path.

Related errors


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