{"record":{"id":"f65c5733482581be","repo":"evanw/esbuild","slug":"expected-onend-callback-in-plugin-quote-name","errorCode":null,"errorMessage":"Expected onEnd() callback in plugin ${quote(name)} to return an object","messagePattern":"Expected onEnd\\(\\) callback in plugin (.+?) to return an object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/shared/common.ts","lineNumber":1500,"sourceCode":"  }\n\n  let runOnEndCallbacks: RunOnEndCallbacks = (result, done) => done([], [])\n\n  if (onEndCallbacks.length > 0) {\n    runOnEndCallbacks = (result, done) => {\n      (async () => {\n        const onEndErrors: types.Message[] = []\n        const onEndWarnings: types.Message[] = []\n\n        for (const { name, callback, note } of onEndCallbacks) {\n          let newErrors: types.Message[] | undefined\n          let newWarnings: types.Message[] | undefined\n\n          try {\n            const value = await callback(result)\n\n            if (value != null) {\n              if (typeof value !== 'object') throw new Error(`Expected onEnd() callback in plugin ${quote(name)} to return an object`)\n              let keys: OptionKeys = {}\n              let errors = getFlag(value, keys, 'errors', mustBeArray)\n              let warnings = getFlag(value, keys, 'warnings', mustBeArray)\n              checkForInvalidFlags(value, keys, `from onEnd() callback in plugin ${quote(name)}`)\n\n              if (errors != null) newErrors = sanitizeMessages(errors, 'errors', details, name, undefined)\n              if (warnings != null) newWarnings = sanitizeMessages(warnings, 'warnings', details, name, undefined)\n            }\n          } catch (e) {\n            newErrors = [extractErrorMessageV8(e, streamIn, details, note && note(), name)]\n          }\n\n          // Try adding the errors and warnings to the result object, but\n          // continue if something goes wrong. If error-reporting has errors\n          // then nothing can help us...\n          if (newErrors) {\n            onEndErrors.push(...newErrors)\n            try {","sourceCodeStart":1482,"sourceCodeEnd":1518,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/lib/shared/common.ts#L1482-L1518","documentation":"When an `onEnd` callback runs (lib/shared/common.ts:1500), its awaited return value must be `null`/`undefined` or an object optionally containing `errors` and `warnings` arrays (allowing the plugin to inject messages into the final result). A truthy non-object (string, number, array, boolean) triggers this error.","triggerScenarios":"An onEnd callback that returns a string summary, a number, or an array instead of an object.","commonSituations":"Returning a build summary string; returning a Promise resolving to a non-object; forgetting to return `null` after analysis.","solutions":["Return `null`/`undefined` when you have no messages, or `{ errors: [...], warnings: [...] }` to add them.","If you need to emit a custom message, push it into the `errors`/`warnings` arrays on the returned object."],"exampleFix":"// before\nbuild.onEnd(result => `built ${result.outputFiles.length} files`);\n// after\nbuild.onEnd(result => { console.log(`built ${result.outputFiles.length} files`); return null; });","handlingStrategy":"type-guard","validationCode":"function wrapOnEnd(cb) {\n  return async (result) => { const v = await cb(result); return v == null || (typeof v === 'object' && !Array.isArray(v)) ? v : null; };\n}","typeGuard":"function isOnEndResult(v: any): v is { errors?: any[]; warnings?: any[] } | null {\n  return v == null || (typeof v === 'object' && !Array.isArray(v));\n}","tryCatchPattern":null,"preventionTips":["Return null when not injecting messages.","Put custom diagnostics in the errors/warnings arrays."],"tags":["plugins","onend","validation","callback-contract"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}