evanw/esbuild · error · Error

key in object must be a string

Error message

key ${quote(key)} in object ${quote(property)} must be a string

What it means

The helper `sanitizeStringMap` (lib/shared/common.ts:1822) validates import-attribute objects (the `with` field) returned by onResolve callbacks or passed to `resolve()`. It iterates keys and throws (lib/shared/common.ts:1826) if any value's `typeof` is not `'string'`. The quoted key and property name identify exactly which entry was wrong.

Solutions

  1. Ensure every value in the `with` object is a string, e.g. `{ with: { type: 'json' } }`.
  2. Coerce values with `String(v)` before returning if they are scalar.

Example fix

// before
return { path, with: { type: 1 } };
// after
return { path, with: { type: 'json' } };
Defensive patterns

Strategy: validation

Validate before calling

function cleanStringMap(m: Record<string, any>): Record<string, string> {
  const out: Record<string,string> = {};
  for (const k in m) { if (typeof m[k] !== 'string') throw new Error(`value for ${k} must be string`); out[k] = m[k]; }
  return out;
}

Type guard

function isStringMap(m: any): m is Record<string, string> {
  return typeof m === 'object' && m !== null && Object.values(m).every(v => typeof v === 'string');
}

Prevention

When it happens

Trigger: Returning a `with`/import-attributes object from onResolve where a value is a number, boolean, object, or array instead of a string.

Common situations: Passing structured import attributes (e.g. `{ with: { type: { x: 1 } } }`); forwarding a parsed JSON object whose values aren't strings; using boolean attributes.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/616cd557bc8c5eb8. Report an issue: GitHub.

Appendix: source

Thrown at lib/shared/common.ts:1826

  }

  return messagesClone
}

function sanitizeStringArray(values: any[], property: string): string[] {
  const result: string[] = []
  for (const value of values) {
    if (typeof value !== 'string') throw new Error(`${quote(property)} must be an array of strings`)
    result.push(value)
  }
  return result
}

function sanitizeStringMap(map: Record<string, any>, property: string): Record<string, string> {
  const result: Record<string, string> = Object.create(null)
  for (const key in map) {
    const value = map[key]
    if (typeof value !== 'string') throw new Error(`key ${quote(key)} in object ${quote(property)} must be a string`)
    result[key] = value
  }
  return result
}

function convertOutputFiles({ path, contents, hash }: protocol.BuildOutputFile): types.OutputFile {
  // The text is lazily-generated for performance reasons. If no one asks for
  // it, then it never needs to be generated.
  let text: string | null = null
  return {
    path,
    contents,
    hash,
    get text() {
      // People want to be able to set "contents" and have esbuild automatically
      // derive "text" for them, so grab the contents off of this object instead
      // of using our original value.
      const binary = this.contents

View on GitHub (pinned to f6058f8364)