{"id":"da56e5a5331c0cc6","repo":"evanw/esbuild","slug":"expected-onresolve-callback-in-plugin-quote-na","errorCode":null,"errorMessage":"Expected onResolve() callback in plugin ${quote(name)} to return an object","messagePattern":"Expected onResolve\\(\\) callback in plugin (.+?) to return an object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/shared/common.ts","lineNumber":1395,"sourceCode":"  }\n\n  requestCallbacks['on-resolve'] = async (id, request: protocol.OnResolveRequest) => {\n    let response: protocol.OnResolveResponse = {}, name = '', callback, note\n    for (let id of request.ids) {\n      try {\n        ({ name, callback, note } = onResolveCallbacks[id])\n        let result = await callback({\n          path: request.path,\n          importer: request.importer,\n          namespace: request.namespace,\n          resolveDir: request.resolveDir,\n          kind: request.kind,\n          pluginData: details.load(request.pluginData),\n          with: request.with,\n        })\n\n        if (result != null) {\n          if (typeof result !== 'object') throw new Error(`Expected onResolve() callback in plugin ${quote(name)} to return an object`)\n          let keys: OptionKeys = {}\n          let pluginName = getFlag(result, keys, 'pluginName', mustBeString)\n          let path = getFlag(result, keys, 'path', mustBeString)\n          let namespace = getFlag(result, keys, 'namespace', mustBeString)\n          let suffix = getFlag(result, keys, 'suffix', mustBeString)\n          let external = getFlag(result, keys, 'external', mustBeBoolean)\n          let sideEffects = getFlag(result, keys, 'sideEffects', mustBeBoolean)\n          let pluginData = getFlag(result, keys, 'pluginData', canBeAnything)\n          let errors = getFlag(result, keys, 'errors', mustBeArray)\n          let warnings = getFlag(result, keys, 'warnings', mustBeArray)\n          let watchFiles = getFlag(result, keys, 'watchFiles', mustBeArrayOfStrings)\n          let watchDirs = getFlag(result, keys, 'watchDirs', mustBeArrayOfStrings)\n          checkForInvalidFlags(result, keys, `from onResolve() callback in plugin ${quote(name)}`)\n\n          response.id = id\n          if (pluginName != null) response.pluginName = pluginName\n          if (path != null) response.path = path\n          if (namespace != null) response.namespace = namespace","sourceCodeStart":1377,"sourceCodeEnd":1413,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/lib/shared/common.ts#L1377-L1413","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap the result: onResolve(args => ({ path: args.path.replace(/^old/, 'new') })).","To mark external: onResolve(args => ({ external: true })).","Return undefined/null when the plugin declines to handle the import."],"exampleFix":"// before\nbuild.onResolve({ filter: /^old:/ }, args => args.path.replace(/^old:/, ''));\n// after\nbuild.onResolve({ filter: /^old:/ }, args => ({ path: args.path.replace(/^old:/, '') }));","handlingStrategy":"validation","validationCode":"function wrapOnResolve(build, opts, cb) {\n  build.onResolve(opts, async (args) => {\n    const r = await cb(args);\n    if (r != null && typeof r !== 'object') {\n      throw new TypeError('onResolve callback must return an object { path?, external?, ... } or void');\n    }\n    return r as any;\n  });\n}","typeGuard":"function isResolveResult(v): v is import('esbuild').OnResolveResult | null | undefined {\n  return v == null || (typeof v === 'object' && typeof (v as any).then !== 'function');\n}","tryCatchPattern":null,"preventionTips":["Return { path } not a bare string from onResolve callbacks.","Return undefined to defer to the next plugin/esbuild's resolver.","Wrap external flags as { external: true }."],"tags":["plugins","onresolve","return-value","validation"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}