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

  1. Use async `esbuild.formatMessages(messages, opts)`.
  2. Mark esbuild external for browser builds so server code keeps the Node entry.
  3. If formatting must be synchronous in the browser, pre-format messages on the server and ship the strings.
  4. 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

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


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