evanw/esbuild · error · Error

The "transformSync" API only works in node

Error message

The "transformSync" API only works in node

What it means

Same stub pattern as `buildSync`: the browser entry (`lib/npm/browser.ts:37`) exports `transformSync` as an unconditional thrower. `transformSync` needs `execFileSync` against the native esbuild binary or a worker-thread sync bridge, neither of which exists in a browser. Calling it means the browser build was loaded.

Source

Thrown at lib/npm/browser.ts:37

export let context: typeof types.context = (options: types.BuildOptions) =>
  ensureServiceIsRunning().context(options)

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

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Use the async `esbuild.transform(code, opts)` (Promise-based) which works in the browser after `initialize`.
  2. Mark `esbuild` as external in your browser bundler so the Node build is retained for server code.
  3. Switch to `esbuild-wasm` with `initialize({ wasmURL })` if you need transformation in the browser.
  4. Gate the call behind a Node environment check (`if (typeof window === 'undefined')`).

Example fix

// before
const out = esbuild.transformSync(ts, { loader: 'ts' })

// after
const out = await esbuild.transform(ts, { loader: 'ts' })
Defensive patterns

Strategy: validation

Validate before calling

function transform(code, opts) {
  if (typeof window !== 'undefined') {
    return esbuild.transform(code, opts) // async
  }
  return Promise.resolve(esbuild.transformSync(code, opts))
}

Type guard

function isNodeSyncCapable(): boolean {
  return typeof process !== 'undefined' && !!process.versions?.node && typeof window === 'undefined'
}

Try / catch

try {
  return esbuild.transformSync(code, opts)
} catch (e) {
  if (/only works in node/.test((e as Error).message)) {
    return await esbuild.transform(code, opts)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling `esbuild.transformSync(code, opts)` when `esbuild` resolves to the browser build. Same resolution causes as error 1: bundlers targeting the browser field, or running in a browser/worker/edge runtime.

Common situations: SSR frameworks that bundle server code into a browser-evaluable module; React-router style code sharing where `transformSync` runs in a shared utility loaded on both sides; misconfigured `resolve.mainFields`/`browser` field in webpack.

Related errors


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