evanw/esbuild · error · Error

Expected onResolve() callback in plugin ${quote(name)} to re

Error message

Expected onResolve() callback in plugin ${quote(name)} to return an object

What it means

An onResolve callback can return an object describing how to resolve the matched import ({ path?, namespace?, external?, errors?, warnings?, ... }) or nothing to defer to the next plugin. lib/shared/common.ts:1395 throws when the callback returns a non-null non-object — the protocol can only serialise the well-known object shape. A bare string path or a boolean external is not accepted; they must be wrapped.

Source

Thrown at lib/shared/common.ts:1395

  }

  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 = namespace

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Wrap the result: onResolve(args => ({ path: args.path.replace(/^old/, 'new') })).
  2. To mark external: onResolve(args => ({ external: true })).
  3. Return undefined/null when the plugin declines to handle the import.

Example fix

// before
build.onResolve({ filter: /^old:/ }, args => args.path.replace(/^old:/, ''));
// after
build.onResolve({ filter: /^old:/ }, args => ({ path: args.path.replace(/^old:/, '') }));
Defensive patterns

Strategy: validation

Validate before calling

function wrapOnResolve(build, opts, cb) {
  build.onResolve(opts, async (args) => {
    const r = await cb(args);
    if (r != null && typeof r !== 'object') {
      throw new TypeError('onResolve callback must return an object { path?, external?, ... } or void');
    }
    return r as any;
  });
}

Type guard

function isResolveResult(v): v is import('esbuild').OnResolveResult | null | undefined {
  return v == null || (typeof v === 'object' && typeof (v as any).then !== 'function');
}

Prevention

When it happens

Trigger: Returning a string path directly: onResolve(args => args.path.replace('old', 'new')). Returning { external: true } is fine, but onResolve(() => true) is not. Returning an array of candidates.

Common situations: Plugin meant to redirect imports returns the bare new path string. Author confuses onResolve semantics with onEnd (which returns { errors, warnings }). Refactor that returns the raw output of String.replace.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/da56e5a5331c0cc6.json. Report an issue: GitHub.