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

  1. Return `null`/`undefined` when you have no messages, or `{ errors: [...], warnings: [...] }` to add them.
  2. 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

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


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)