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.contextView on GitHub (pinned to 6ff1d8b0d8)
Solutions
- Use the async `esbuild.transform(code, opts)` (Promise-based) which works in the browser after `initialize`.
- Mark `esbuild` as external in your browser bundler so the Node build is retained for server code.
- Switch to `esbuild-wasm` with `initialize({ wasmURL })` if you need transformation in the browser.
- 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
- Use async transform() in shared/isomorphic code.
- Mark esbuild external for browser bundles.
- Keep transformSync calls in Node-only modules.
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
- The "buildSync" API only works in node
- The "formatMessagesSync" 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/0784f91e3a967515.json.
Report an issue: GitHub.