evanw/esbuild · error · Error
Expected onEnd() callback in plugin
Error message
Expected onEnd() callback in plugin ${quote(name)} to return an object What it means
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.
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.
Example fix
// before
build.onEnd(result => `built ${result.outputFiles.length} files`);
// after
build.onEnd(result => { console.log(`built ${result.outputFiles.length} files`); return null; }); Defensive patterns
Strategy: type-guard
Validate before calling
function wrapOnEnd(cb) {
return async (result) => { const v = await cb(result); return v == null || (typeof v === 'object' && !Array.isArray(v)) ? v : null; };
} Type guard
function isOnEndResult(v: any): v is { errors?: any[]; warnings?: any[] } | null {
return v == null || (typeof v === 'object' && !Array.isArray(v));
} Prevention
- Return null when not injecting messages.
- Put custom diagnostics in the errors/warnings arrays.
When it happens
Trigger: An onEnd callback that returns a string summary, a number, or an array instead of an object.
Common situations: Returning a build summary string; returning a Promise resolving to a non-object; forgetting to return `null` after analysis.
Related errors
- Expected onLoad() callback in plugin
- Expected onResolve() callback in plugin
- Expected onStart() callback in plugin
- key in object must be a string
- Must specify "kind" when calling "resolve"
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/f65c5733482581be.
Report an issue: GitHub.
Appendix: source
Thrown at lib/shared/common.ts:1500
}
let runOnEndCallbacks: RunOnEndCallbacks = (result, done) => done([], [])
if (onEndCallbacks.length > 0) {
runOnEndCallbacks = (result, done) => {
(async () => {
const onEndErrors: types.Message[] = []
const onEndWarnings: types.Message[] = []
for (const { name, callback, note } of onEndCallbacks) {
let newErrors: types.Message[] | undefined
let newWarnings: types.Message[] | undefined
try {
const value = await callback(result)
if (value != null) {
if (typeof value !== 'object') throw new Error(`Expected onEnd() callback in plugin ${quote(name)} to return an object`)
let keys: OptionKeys = {}
let errors = getFlag(value, keys, 'errors', mustBeArray)
let warnings = getFlag(value, keys, 'warnings', mustBeArray)
checkForInvalidFlags(value, keys, `from onEnd() callback in plugin ${quote(name)}`)
if (errors != null) newErrors = sanitizeMessages(errors, 'errors', details, name, undefined)
if (warnings != null) newWarnings = sanitizeMessages(warnings, 'warnings', details, name, undefined)
}
} catch (e) {
newErrors = [extractErrorMessageV8(e, streamIn, details, note && note(), name)]
}
// Try adding the errors and warnings to the result object, but
// continue if something goes wrong. If error-reporting has errors
// then nothing can help us...
if (newErrors) {
onEndErrors.push(...newErrors)
try {View on GitHub (pinned to f6058f8364)