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
- Ensure every value in the `with` object is a string, e.g. `{ with: { type: 'json' } }`.
- 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
- Keep import-attribute values as strings only.
- Coerce scalars with String(v) before assignment.
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
- must be an array of strings
- Expected onEnd() callback in plugin
- Expected onLoad() callback in plugin
- Expected onResolve() callback in plugin
- Expected onStart() callback in plugin
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.contentsView on GitHub (pinned to f6058f8364)