Skip to main content
Bun provides a universal plugin API that works in both the bundler (Bun.build()) and the runtime (via preload in bunfig.toml). Plugins intercept imports and perform custom loading logic — reading files, transforming code, resolving modules to virtual paths, and more.

Creating a plugin

A plugin is a plain object with a name string and a setup function:
myPlugin.ts
Pass it to Bun.build():

SVG plugin example

A complete plugin that imports SVG files as string exports:

Lifecycle hooks

onStart

Called once when the bundler starts a new bundle. Useful for setup, logging, or initializing shared state.
onStart callbacks can be async. The bundler waits for all onStart callbacks to resolve before continuing.
onStart callbacks cannot modify build.config. Mutate the config directly inside setup() if needed.

onResolve

Intercepts module resolution. Lets you redirect imports to different paths or virtual namespaces.
The callback receives { path, importer } and can return { path, namespace? } to override resolution, or undefined to let other resolvers handle it.

onLoad

Intercepts module loading. Lets you replace or transform a file’s contents before it is parsed.
The callback receives { path, importer, namespace, kind } and can return: Available loaders: js, jsx, ts, tsx, json, jsonc, toml, yaml, file, napi, wasm, text, css, html

onEnd

Called after the bundle is complete. Receives the full BuildOutput object.
onEnd callbacks can be async. The promise returned by Bun.build() does not resolve until all onEnd callbacks have completed — useful for post-build tasks like uploading artifacts:

onBeforeParse (native plugins only)

A native plugin hook that runs on any thread before a file is parsed. Only available to NAPI modules. See native plugins below.

Namespaces

Every module has a namespace. Common namespaces:
  • "file" — files on disk (default)
  • "bun" — Bun built-in modules (bun:test, bun:sqlite)
  • "node" — Node.js built-in modules (node:fs, node:path)
Use namespaces to create virtual modules:

.defer() in onLoad

The defer argument in onLoad returns a Promise that resolves when all other modules have been loaded. This lets you produce module contents that depend on the full import graph.
.defer() can only be called once per onLoad callback.

Native plugins

JavaScript plugins are single-threaded. Native plugins are NAPI modules that run on multiple threads alongside Bun’s parser, offering significantly better performance. Native plugins implement the onBeforeParse lifecycle hook, which is called before a file is parsed.

Creating a native plugin in Rust

lib.rs
index.ts

Runtime plugins

Plugins can also be used at runtime (outside the bundler) to intercept import and require calls in Bun’s module loader:
preload.ts
Register it as a preload in bunfig.toml:
bunfig.toml
Or pass it via CLI:
Runtime plugins registered with plugin() are active for the current Bun process only. Bundler plugins registered via Bun.build() run only during bundling.

Plugin type reference