| | |

esbuild 28 ⚡ Cheatsheet

⏱️ Estimated read: 7 min | |

This cheatsheet covers esbuild 0.28.x, the current stable line as of mid-2026 . It is scoped to the decisions you make mid-build: the CLI flags, the API entry points, the plugin hooks, the loader mappings, and the incremental lifecycle. The version numbers matter less than the shape of the tool, but the shape has settled, and this is the shape.


The Two Entry Points

esbuild has two interfaces. The CLI is a binary that parses flags and runs a build. The JavaScript API is a module that imports esbuild and calls a function. They share the same engine, and the flags map to the options. The choice is the lifecycle, not the output.

InterfaceEntryUse case
CLIesbuild src/app.ts --bundleOne-shot build, simple flags
APIimport * as esbuild from 'esbuild'Plugins, context, result inspection
Transform APIesbuild.transform(code, { loader: 'ts' })In-memory single-file transform

The CLI is the one-liner. The API is the module. The transform API is the primitive that does not touch the file system.


The Build vs Context Distinction

Since 0.17.0, build() is for one-shot builds and context() is for long-lived builds. The watch and serve options were removed from build(), and the context object is the replacement.

FunctionReturnsWatch/Serve
esbuild.build(options)Promise<BuildResult>❌ Removed
esbuild.context(options)Promise<BuildContext>✅ ctx.watch(), ctx.serve()

The context object holds the incremental state. The rebuild(), watch(), and serve() methods operate on it. The dispose() method is the single cleanup path.

// One-shot
const result = await esbuild.build({ entryPoints: ['app.ts'], bundle: true, outdir: 'dist' });

// Long-lived
const ctx = await esbuild.context({ entryPoints: ['app.ts'], bundle: true, outdir: 'dist' });
await ctx.rebuild();       // initial build
await ctx.watch();         // start watcher
await ctx.serve({ servedir: 'www' }); // dev server
await ctx.dispose();       // cleanup

The Loaders

The loader tells esbuild how to parse a file. The --loader:.ext=name flag maps an extension to a loader. The loader option in the API does the same.

LoaderContent typeOutput
jsJavaScriptJS module
jsxJSXJS module
tsTypeScriptJS module
tsxTSXJS module
cssCSSCSS
jsonJSONJS module (default export)
textRaw stringJS module (default export)
binaryUint8ArrayJS module (default export)
fileFile pathURL string
dataurlData URLData URL string
copyCopy fileNo transformation
base64Base64 stringBase64 string

The text, binary, file, and dataurl loaders are the asset loaders. The js, ts, and jsx loaders are the code loaders.


The Plugin Hooks

A plugin is a setup function with onResolve and onLoad. The two hooks are the entire extension surface.

HookWhenReturns
onResolveEvery import pathpath, namespace, external, or undefined
onLoadEvery unique path/namespacecontents, loader, or undefined
onStartBuild start—
onEndBuild end—
onDisposeContext disposal—

The onResolve hook turns a specifier into a path. The onLoad hook turns a path into content. The namespace is the link: the onResolve returns a custom namespace, and the onLoad filters on it.

const plugin = {
  name: 'my-plugin',
  setup(build) {
    build.onResolve({ filter: /^env$/ }, () => ({ path: 'env', namespace: 'env-ns' }));
    build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
      contents: JSON.stringify(process.env),
      loader: 'json',
    }));
    build.onEnd((result) => console.log(result.errors.length));
  },
};

The onEnd callback fires after every build, including the first. It is the replacement for the removed onRebuild callback.


The Filter Syntax

Both hooks require a filter RegExp. The filter is evaluated in Go, and the callback is only invoked when the filter matches.

FilterMatch
/\.ts$/Files ending in .ts
/^env$/The exact specifier env
/.*/Everything

The filter syntax follows Go’s regexp engine. No look-ahead, no look-behind, no backreferences. An invalid filter causes a build error.


The External and Alias Options

The external array marks packages as external. The alias map rewrites specifiers. The define map substitutes globals.

OptionTypePurpose
externalstring[]Packages to leave unbundled
aliasRecord<string, string>Specifier rewrite
defineRecord<string, string>Global substitution
injectstring[]Files to inject imports from
await esbuild.build({
  external: ['react', 'react-dom'],
  alias: { 'old-lib': 'new-lib' },
  define: { 'process.env.NODE_ENV': JSON.stringify('production') },
  inject: ['./shims.js'],
});

The define values must be JSON.stringify-wrapped for strings. The inject files are imported into every module that uses the injected symbols.


The Output Configuration

The output options control the format, the target, and the file names.

OptionValuesDefault
formatiife, cjs, esmiife (browser), cjs (node)
platformbrowser, node, neutralbrowser
targetes2015, es2020, chrome100, etc.esnext
outdirDirectory—
outfileFile—
splittingbooleanfalse
minifybooleanfalse
sourcemapboolean, inline, external, bothfalse

The splitting option requires format: 'esm' and outdir. The outfile option is incompatible with splitting.


The Chunking and Naming

The entryNames, chunkNames, and assetNames templates control the output filenames. The [hash] placeholder is the cache-busting mechanism.

OptionPlaceholdersApplies to
entryNames[dir], [name], [hash], [ext]Entry points
chunkNames[name], [hash], [ext]Shared chunks
assetNames[dir], [name], [hash], [ext]File loader assets
build: {
  entryNames: '[dir]/[name]-[hash]',
  chunkNames: 'chunks/[name]-[hash]',
  assetNames: 'assets/[ext]/[name]-[hash]',
}

The [dir] placeholder is not supported in chunkNames. The [hash] placeholder is what makes long-term caching possible.


The Metafile

The metafile: true option produces a JSON object with inputs and outputs. The inputs object maps every module to its bytes and imports. The outputs object maps every output to its bytesInOutput and entryPoint.

const result = await esbuild.build({ metafile: true, /* ... */ });
console.log(result.metafile.inputs);
console.log(result.metafile.outputs);

The metafile is the data. The visualizers (esbuild-visualizer, esbuild-lazy-analyzer, @rnx-kit/esbuild-bundle-analyzer) are consumers of the data. The metafile is not enabled by default because it adds a small amount of work and produces a file that is not needed for the production output.


The CLI Quick Reference

# One-shot bundle
esbuild src/app.ts --bundle --outdir=dist --minify

# Watch
esbuild src/app.ts --bundle --outdir=dist --watch

# Serve
esbuild src/app.ts --bundle --outdir=www --serve --servedir=www

# External
esbuild src/app.ts --bundle --external:react --outfile=dist/app.js

# Loader
esbuild src/app.ts --bundle --loader:.png=file --outdir=dist

# Metafile
esbuild src/app.ts --bundle --metafile=stats.json --outfile=dist/app.js

# Target
esbuild src/app.ts --bundle --target=es2020 --outfile=dist/app.js

The CLI performs the initial build automatically. The API does not; the rebuild() call is required.


The Common Pitfalls

PitfallCauseFix
watch: true rejectedRemoved in 0.17.0Use context() + watch()
The process does not exitThe watcher holds the handleCall ctx.dispose()
A plugin is not calledThe filter does not matchCheck the regexp against the path
The virtual module is not loadedThe namespace does not matchCheck the namespace string
The define value is a raw stringJSON.stringify not usedWrap the value
The onLoad returns a stringThe callback must return an objectReturn { contents, loader }

The Version Note

esbuild 0.28.0 added support for the with { type: 'text' } import proposal and integrity checks for the fallback download path . The OpenBSD ports tree shows 0.28.1 as the current version as of June 2026 . The version line is still 0.x, which means the API is not semver-stable in the strict sense, but the surface has been stable for years. The with { type: 'text' } import is the newest syntax addition, and it is the same as the existing text loader.


Summary

AreaKey takeaway
Entry pointsbuild() for one-shot, context() for watch/serve
PluginsonResolve + onLoad, connected by namespace
FiltersGo regexp, no look-ahead or backreferences
Externalexternal array, alias map, define map
Outputformat, platform, target, outdir/outfile
Chunkingsplitting: true + format: 'esm' + outdir
NamingentryNames, chunkNames, assetNames
Metafilemetafile: true for analysis
Version0.28.x, with { type: 'text' } support

Remember: esbuild is a bundler and a transformer with two interfaces and one engine. The CLI is the one-liner for the simple build. The API is the module for the plugin, the context, and the result object. The context is the incremental lifecycle, and the onEnd callback is the post-build hook. The filter is the performance mechanism, and the namespace is the virtual-module mechanism. The metafile is the analysis data, and the visualizers are its consumers. The tool is small, and the surface is stable.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!