evanw/esbuild · error · Error
The "formatMessagesSync" API only works in node
Error message
The "formatMessagesSync" API only works in node
What it means
The browser entry (`lib/npm/browser.ts:41`) stubs `formatMessagesSync` to throw because formatting messages synchronously requires the native binary via `execFileSync`. The async `formatMessages` works in the browser via the wasm service; the sync variant cannot.
Source
Thrown at lib/npm/browser.ts:41
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
ensureServiceIsRunning().transform(input, options)
export const formatMessages: typeof types.formatMessages = (messages, options) =>
ensureServiceIsRunning().formatMessages(messages, options)
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
ensureServiceIsRunning().analyzeMetafile(metafile, options)
export const buildSync: typeof types.buildSync = () => {
throw new Error(`The "buildSync" API only works in node`)
}
export const transformSync: typeof types.transformSync = () => {
throw new Error(`The "transformSync" API only works in node`)
}
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
throw new Error(`The "formatMessagesSync" API only works in node`)
}
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
throw new Error(`The "analyzeMetafileSync" API only works in node`)
}
export const stop = () => {
if (stopService) stopService()
return Promise.resolve()
}
interface Service {
build: typeof types.build
context: typeof types.context
transform: typeof types.transform
formatMessages: typeof types.formatMessages
analyzeMetafile: typeof types.analyzeMetafile
}View on GitHub (pinned to 6ff1d8b0d8)
Solutions
- Use async `esbuild.formatMessages(messages, opts)`.
- Mark esbuild external for browser builds so server code keeps the Node entry.
- If formatting must be synchronous in the browser, pre-format messages on the server and ship the strings.
- Use `esbuild-wasm` with `initialize` for in-browser async formatting.
Example fix
// before
const formatted = esbuild.formatMessagesSync(msgs, { kind: 'error' })
// after
const formatted = await esbuild.formatMessages(msgs, { kind: 'error' }) Defensive patterns
Strategy: validation
Validate before calling
function format(msgs, opts) {
return typeof window === 'undefined'
? Promise.resolve(esbuild.formatMessagesSync(msgs, opts))
: esbuild.formatMessages(msgs, opts)
} Type guard
function isBrowser(): boolean {
return typeof window !== 'undefined' || (typeof self !== 'undefined' && typeof importScripts === 'function')
} Try / catch
try {
return esbuild.formatMessagesSync(msgs, opts)
} catch (e) {
if (/only works in node/.test((e as Error).message)) {
return await esbuild.formatMessages(msgs, opts)
}
throw e
} Prevention
- Default to async formatMessages in code that may run in browsers.
- Mark esbuild external in browser-targeted builds.
- Pre-format messages server-side when overlay rendering must be synchronous.
When it happens
Trigger: Calling `esbuild.formatMessagesSync(messages, opts)` from code that resolved the browser build of esbuild.
Common situations: Pretty-printing esbuild diagnostics synchronously in a browser dev overlay; bundler misconfiguration picking the `browser` field; importing esbuild in a frontend error reporter.
Related errors
- The "buildSync" API only works in node
- The "transformSync" API only works in node
- The "analyzeMetafileSync" API only works in node
- You need to wait for the promise returned from "initialize"
- Must provide either the "wasmURL" option or the "wasmModule"
AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03).
Data as JSON: /data/errors/7fa74daca8227a7c.json.
Report an issue: GitHub.