evanw/esbuild · error · Error
must be an array of strings
Error message
${quote(property)} must be an array of strings What it means
The helper `sanitizeStringArray` (lib/shared/common.ts:1813) is used to validate `watchFiles` and `watchDirs` returned by onResolve/onLoad callbacks. It iterates the array and throws (lib/shared/common.ts:1816) for any element whose `typeof` is not `'string'`. The quoted property name tells you which field was malformed.
Solutions
- Ensure every element of `watchFiles`/`watchDirs` is a string (e.g. `watchFiles: files.map(f => f.path)`).
- Filter out nullish entries before returning: `arr.filter(Boolean)`.
Example fix
// before
return { contents, watchFiles: [{ path: depPath }] };
// after
return { contents, watchFiles: [depPath] }; Defensive patterns
Strategy: validation
Validate before calling
function cleanStringArray(arr: any[]): string[] {
return arr.map((v, i) => { if (typeof v !== 'string') throw new Error(`element ${i} is not a string`); return v; });
} Type guard
function isStringArray(a: any): a is string[] {
return Array.isArray(a) && a.every(v => typeof v === 'string');
} Prevention
- Map path objects to strings before returning watchFiles/watchDirs.
- Filter nullish entries with arr.filter(x => typeof x === 'string').
When it happens
Trigger: Returning `watchFiles` or `watchDirs` from an onResolve/onLoad callback where at least one entry is a number, object, null, or undefined instead of a string.
Common situations: Pushing a path object (e.g. `{ path }`) instead of the string path; mixing in `undefined` from an optional field; returning file descriptors instead of paths.
Related errors
- key in object must be a string
- 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/f0d60f2a0f1912a4.
Report an issue: GitHub.
Appendix: source
Thrown at lib/shared/common.ts:1816
messagesClone.push({
id: id || '',
pluginName: pluginName || fallbackPluginName,
text: text || '',
location: sanitizeLocation(location, where, terminalWidth),
notes: notesClone,
detail: stash ? stash.store(detail) : -1,
})
index++
}
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.View on GitHub (pinned to f6058f8364)