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

  1. Return an object such as `{ path: resolvedPath, namespace: 'file' }`, or `null` to defer to the next handler.
  2. 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

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


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

View on GitHub (pinned to f6058f8364)