{"id":"debda113a733eee3","repo":"evanw/esbuild","slug":"plugin-is-missing-a-setup-function","errorCode":null,"errorMessage":"Plugin is missing a setup function","messagePattern":"Plugin is missing a setup function","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/shared/common.ts","lineNumber":1227,"sourceCode":"  } = {}\n\n  let onDisposeCallbacks: (() => void)[] = []\n  let nextCallbackID = 0\n  let i = 0\n  let requestPlugins: protocol.BuildPlugin[] = []\n  let isSetupDone = false\n\n  // Clone the plugin array to guard against mutation during iteration\n  plugins = [...plugins]\n\n  for (let item of plugins) {\n    let keys: OptionKeys = {}\n    if (typeof item !== 'object') throw new Error(`Plugin at index ${i} must be an object`)\n    const name = getFlag(item, keys, 'name', mustBeString)\n    if (typeof name !== 'string' || name === '') throw new Error(`Plugin at index ${i} is missing a name`)\n    try {\n      let setup = getFlag(item, keys, 'setup', mustBeFunction)\n      if (typeof setup !== 'function') throw new Error(`Plugin is missing a setup function`)\n      checkForInvalidFlags(item, keys, `on plugin ${quote(name)}`)\n\n      let plugin: protocol.BuildPlugin = {\n        name,\n        onStart: false,\n        onEnd: false,\n        onResolve: [],\n        onLoad: [],\n      }\n      i++\n\n      let resolve = (path: string, options: types.ResolveOptions = {}): Promise<types.ResolveResult> => {\n        if (!isSetupDone) throw new Error('Cannot call \"resolve\" before plugin setup has completed')\n        if (typeof path !== 'string') throw new Error(`The path to resolve must be a string`)\n        let keys: OptionKeys = Object.create(null)\n        let pluginName = getFlag(options, keys, 'pluginName', mustBeString)\n        let importer = getFlag(options, keys, 'importer', mustBeString)\n        let namespace = getFlag(options, keys, 'namespace', mustBeString)","sourceCodeStart":1209,"sourceCodeEnd":1245,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/lib/shared/common.ts#L1209-L1245","documentation":"The 'setup' function is the entry point of an esbuild plugin; esbuild calls it once per build with the BuildPlugin interface (onResolve, onLoad, onStart, onEnd, resolve, etc.). lib/shared/common.ts:1227 throws when getFlag(setup, mustBeFunction) is not a function, because there is nothing else to invoke. This is checked after name (45) and is the last structural guard before the plugin is registered.","triggerScenarios":"Plugin object with name but no setup. setup defined as an arrow stored in a const that was never assigned: const setup; { name, setup }. setup accidentally renamed (e.g. setupFunction) so the actual 'setup' key is undefined.","commonSituations":"Plugin written as a class whose setup is on the prototype (instance method) but the raw class is passed instead of an instance. Minifier strips an unused-looking 'setup' export. Plugin factory returns { name, onResolve } forgetting setup.","solutions":["Define setup(build) { ... } as a top-level method on the plugin object.","If using a class, pass new MyPlugin() (an instance) so its setup method resolves.","Verify the key is literally 'setup' (not setupFn, init, configure)."],"exampleFix":"// before\nexport default {\n  name: 'my-plugin',\n  // forgot setup; only has helpers\n  resolveId() { /* ... */ },\n};\n// after\nexport default {\n  name: 'my-plugin',\n  setup(build) {\n    build.onResolve({ filter: /.*/ }, () => { /* ... */ });\n  },\n};","handlingStrategy":"type-guard","validationCode":"function assertPlugin(p) {\n  if (typeof p.setup !== 'function') {\n    throw new Error(`Plugin ${p.name} is missing a setup(build) function`);\n  }\n  return p;\n}","typeGuard":"function hasSetup(p): p is { name: string; setup: Function } {\n  return !!p && typeof p.name === 'string' && typeof p.setup === 'function';\n}","tryCatchPattern":null,"preventionTips":["Author plugins with a literal setup(build){} method on the exported object.","Pass instances, not classes, when using class-based plugins.","Verify the property is literally 'setup' (not setupFn/init)."],"tags":["plugins","validation","api-misuse"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}