evanw/esbuild · error · Error
Expected onResolve() callback in plugin
Error message
Expected onResolve() callback in plugin ${quote(name)} to return an object What it means
When an `onResolve` callback runs (lib/shared/common.ts:1399), its awaited return value must be `null`/`undefined` (skip/defer) or an object describing the resolution (fields like `path`, `namespace`, `external`, `errors`, etc.). A truthy non-object (string, number, array, boolean) triggers this error, which is caught and returned as a resolve error for the plugin.
Solutions
- Return an object such as `{ path: resolvedPath, namespace: 'file' }`, or `null` to defer to the next handler.
- Return `{ external: true }` to mark the import external, or `{ errors: [...] }` to report a failure.
Example fix
// before
build.onResolve({ filter: /.*/ }, args => args.path.replace(/^app:/, ''));
// after
build.onResolve({ filter: /.*/ }, args => ({ path: args.path.replace(/^app:/, '') })); Defensive patterns
Strategy: type-guard
Validate before calling
function wrapOnResolve(cb) {
return async (args) => { const r = await cb(args); if (r != null && typeof r !== 'object') throw new Error('onResolve must return object or null'); return r; };
} Type guard
function isOnResolveResult(v: any): v is { path?: string; namespace?: string; external?: boolean } | null {
return v == null || (typeof v === 'object' && !Array.isArray(v));
} Prevention
- Always return an object literal { path } or null.
- Never return a bare path string.
When it happens
Trigger: An onResolve callback that returns a string path or other non-object instead of `{ path: ... }`.
Common situations: Returning a bare path string instead of an object; returning the args object directly without reshaping; confusion with bundlers where handlers return strings.
Related errors
- Expected onEnd() callback in plugin
- Expected onLoad() callback in plugin
- Expected onStart() callback in plugin
- onResolve() call is missing a filter
- Invalid path suffix returned from plugin (must start with…
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/da56e5a5331c0cc6.
Report an issue: GitHub.
Appendix: source
Thrown at lib/shared/common.ts:1399
}
requestCallbacks['on-resolve'] = async (id, request: protocol.OnResolveRequest) => {
let response: protocol.OnResolveResponse = {}, name = '', callback, note
for (let id of request.ids) {
try {
({ name, callback, note } = onResolveCallbacks[id])
let result = await callback({
path: request.path,
importer: request.importer,
namespace: request.namespace,
resolveDir: request.resolveDir,
kind: request.kind,
pluginData: details.load(request.pluginData),
with: request.with,
})
if (result != null) {
if (typeof result !== 'object') throw new Error(`Expected onResolve() callback in plugin ${quote(name)} to return an object`)
let keys: OptionKeys = {}
let pluginName = getFlag(result, keys, 'pluginName', mustBeString)
let path = getFlag(result, keys, 'path', mustBeString)
let namespace = getFlag(result, keys, 'namespace', mustBeString)
let suffix = getFlag(result, keys, 'suffix', mustBeString)
let external = getFlag(result, keys, 'external', mustBeBoolean)
let sideEffects = getFlag(result, keys, 'sideEffects', mustBeBoolean)
let pluginData = getFlag(result, keys, 'pluginData', canBeAnything)
let errors = getFlag(result, keys, 'errors', mustBeArray)
let warnings = getFlag(result, keys, 'warnings', mustBeArray)
let watchFiles = getFlag(result, keys, 'watchFiles', mustBeArrayOfStrings)
let watchDirs = getFlag(result, keys, 'watchDirs', mustBeArrayOfStrings)
checkForInvalidFlags(result, keys, `from onResolve() callback in plugin ${quote(name)}`)
response.id = id
if (pluginName != null) response.pluginName = pluginName
if (path != null) response.path = path
if (namespace != null) response.namespace = namespaceView on GitHub (pinned to f6058f8364)