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
- Ensure the function is the LAST argument in the (name, desc, options, fn) overload.
- If you only need name+options+fn, put options third and fn fourth.
- 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
- Always place the handler as the final argument in overloaded register calls.
- Write small wrapper helpers per overload to avoid positional mistakes.
- Cover each overload signature with a registration test.
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.