evanw/esbuild · error · Error

Must specify "kind" when calling "resolve"

Error message

Must specify "kind" when calling "resolve"

What it means

Inside a plugin, build.resolve(path, options) forwards to esbuild's resolver; the 'kind' option tells it the import context (import-statement, entry-point, require-resolve, etc.) because resolution rules differ by kind (e.g. ESM vs CJS). At lib/shared/common.ts:1264 esbuild throws if 'kind' is omitted, since guessing would silently pick the wrong resolution semantics and produce wrong external/path results.

Source

Thrown at lib/shared/common.ts:1264

        let resolveDir = getFlag(options, keys, 'resolveDir', mustBeString)
        let kind = getFlag(options, keys, 'kind', mustBeString)
        let pluginData = getFlag(options, keys, 'pluginData', canBeAnything)
        let importAttributes = getFlag(options, keys, 'with', mustBeObject)
        checkForInvalidFlags(options, keys, 'in resolve() call')

        return new Promise((resolve, reject) => {
          const request: protocol.ResolveRequest = {
            command: 'resolve',
            path,
            key: buildKey,
            pluginName: name,
          }
          if (pluginName != null) request.pluginName = pluginName
          if (importer != null) request.importer = importer
          if (namespace != null) request.namespace = namespace
          if (resolveDir != null) request.resolveDir = resolveDir
          if (kind != null) request.kind = kind
          else throw new Error(`Must specify "kind" when calling "resolve"`)
          if (pluginData != null) request.pluginData = details.store(pluginData)
          if (importAttributes != null) request.with = sanitizeStringMap(importAttributes, 'with')

          sendRequest<protocol.ResolveRequest, protocol.ResolveResponse>(refs, request, (error, response) => {
            if (error !== null) reject(new Error(error))
            else resolve({
              errors: replaceDetailsInMessages(response!.errors, details),
              warnings: replaceDetailsInMessages(response!.warnings, details),
              path: response!.path,
              external: response!.external,
              sideEffects: response!.sideEffects,
              namespace: response!.namespace,
              suffix: response!.suffix,
              pluginData: details.load(response!.pluginData),
            })
          })
        })
      }

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Always pass kind: one of 'entry-point' | 'import-statement' | 'require-call' | 'dynamic-import' | 'require-resolve' | 'css-rule' | 'css-at-rule' | 'js-url' | 'css-url' | 'css-import' | 'json-import'.
  2. Match the kind to how the import appears in source: import x from → 'import-statement', require() → 'require-call'.
  3. Add a TS helper that asserts kind is present: type ResolveKind = ...; and a runtime check.

Example fix

// before
const r = await build.resolve('./mod', { importer: file });
// after
const r = await build.resolve('./mod', { importer: file, kind: 'import-statement' });
Defensive patterns

Strategy: validation

Validate before calling

const RESOLVE_KINDS = new Set([
  'entry-point','import-statement','require-call','dynamic-import',
  'require-resolve','css-rule','css-at-rule','css-url','css-import',
  'js-url','json-import',
]);
function safeResolve(build, path, opts) {
  if (!opts?.kind || !RESOLVE_KINDS.has(opts.kind)) {
    throw new TypeError('build.resolve requires opts.kind from the supported set');
  }
  return build.resolve(path, opts);
}

Type guard

function isResolveKind(v): v is import('esbuild').ResolveKind {
  return typeof v === 'string' && [
    'entry-point','import-statement','require-call','dynamic-import',
    'require-resolve','css-rule','css-at-rule','css-url','css-import',
    'js-url','json-import',
  ].includes(v);
}

Prevention

When it happens

Trigger: Calling build.resolve('./foo') with no options. Calling build.resolve('./foo', { importer }) forgetting kind. Passing kind: undefined explicitly.

Common situations: Plugin does custom resolution and copies a partial options object. Refactor that drops the kind field. Devs reading the type signature ResolveOptions and assuming kind is optional (it's optional in the type but required at runtime for this codepath).

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/4357dea1952cb2c0.json. Report an issue: GitHub.