evanw/esbuild · error · Error
Plugin is missing a setup function
Error message
Plugin is missing a setup function
What it means
During plugin validation (lib/shared/common.ts:1231), after confirming `name`, esbuild reads the `setup` field with `mustBeFunction` and throws if it is not a function. `setup` is the entry point esbuild invokes to register the plugin's hooks (onResolve, onLoad, onStart, onEnd, onDispose).
Solutions
- Define a `setup(build)` function on the plugin object.
- Fix any renamed key (e.g. `install` -> `setup`).
- Validate the plugins array with a type guard before calling build.
Example fix
// before
{ name: 'x', install(build) { /* ... */ } }
// after
{ name: 'x', setup(build) { /* ... */ } } Defensive patterns
Strategy: type-guard
Validate before calling
function ensurePluginSetup(plugins: any[]): void {
plugins.forEach(p => { if (typeof p?.setup !== 'function') throw new Error(`Plugin ${p?.name} is missing a setup function`); });
} Type guard
function pluginHasSetup(p: any): p is { setup: Function } {
return typeof p === 'object' && p !== null && typeof p.setup === 'function';
} Prevention
- Name the hook `setup` exactly — not install/init/apply.
- Centralize plugin authoring in a typed helper to enforce the contract.
When it happens
Trigger: Passing a plugin object whose `setup` is missing, undefined, or a non-function — e.g. `{ name: 'x', install() {} }` (wrong key) or `{ name: 'x' }`.
Common situations: Typo'ing the method name (`init`, `apply`, `install` instead of `setup`); copying a plugin skeleton that omitted the body; spreading defaults that don't include `setup`.
Related errors
- Plugin at index is missing a name
- Plugin at index must be an object
- Expected onEnd() callback in plugin
- Expected onLoad() callback in plugin
- Expected onResolve() callback in plugin
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/debda113a733eee3.
Report an issue: GitHub.
Appendix: source
Thrown at lib/shared/common.ts:1231
} = {}
let onDisposeCallbacks: (() => void)[] = []
let nextCallbackID = 0
let i = 0
let requestPlugins: protocol.BuildPlugin[] = []
let isSetupDone = false
// Clone the plugin array to guard against mutation during iteration
plugins = [...plugins]
for (let item of plugins) {
let keys: OptionKeys = {}
if (typeof item !== 'object') throw new Error(`Plugin at index ${i} must be an object`)
const name = getFlag(item, keys, 'name', mustBeString)
if (typeof name !== 'string' || name === '') throw new Error(`Plugin at index ${i} is missing a name`)
try {
let setup = getFlag(item, keys, 'setup', mustBeFunction)
if (typeof setup !== 'function') throw new Error(`Plugin is missing a setup function`)
checkForInvalidFlags(item, keys, `on plugin ${quote(name)}`)
let plugin: protocol.BuildPlugin = {
name,
onStart: false,
onEnd: false,
onResolve: [],
onLoad: [],
}
i++
let resolve = (path: string, options: types.ResolveOptions = {}): Promise<types.ResolveResult> => {
if (!isSetupDone) throw new Error('Cannot call "resolve" before plugin setup has completed')
if (typeof path !== 'string') throw new Error(`The path to resolve must be a string`)
let keys: OptionKeys = Object.create(null)
let pluginName = getFlag(options, keys, 'pluginName', mustBeString)
let importer = getFlag(options, keys, 'importer', mustBeString)
let namespace = getFlag(options, keys, 'namespace', mustBeString)View on GitHub (pinned to f6058f8364)