Skip to main content
Macros are a mechanism for running JavaScript functions at bundle time. The return value of each function is serialized and inlined directly into the bundled output — the macro’s source code never appears in the final bundle.

Basic example

Consider a function that returns a random number:
random.ts
Use it as a macro with the with { type: "macro" } import attribute:
app.ts
Build it:
The macro runs at bundle time. The output contains the result, not the function:
The source code of random.ts does not appear in the bundle at all.
Macros use import attribute syntax — a Stage 3 TC39 proposal for attaching metadata to import statements. Both with { type: "macro" } and the older assert { type: "macro" } are supported.

When to use macros

Macros are useful when you want to:
  • Embed build-time constants — version numbers, git commit hashes, build timestamps
  • Fetch external data at build time — CMS content, API responses, feature flags
  • Generate code from schemas — introspect a database, generate typed clients
  • Replace one-off build scripts — macros live alongside your source code, run in parallel with the build, and fail the build if they throw
If you’re running a large amount of code at build time, consider a server instead. Macros are not a general-purpose build system.

Real-world examples

Embed the current git commit hash

getGitHash.ts

Fetch data at build time

In this example, an HTTP request happens at bundle time and the result is inlined into the bundle. The fetch call does not appear in the output:
meta.ts
The error branch is also eliminated because the condition is now statically false.

Serializability

Macros must return JSON-serializable values. Bun also handles these special types: Macros can be async. The transpiler awaits the returned promise automatically:

Arguments

Macro arguments must be statically known at bundle time. Dynamic values are not allowed:
If the value is statically known — for example, a constant or the result of another macro — it is allowed:

Dead code elimination

Macros run before dead code elimination. If a macro returns false, the branch that depends on it is removed from the output (when --minify-syntax is enabled):
returnFalse.ts
index.ts

Execution model

Macros are executed by Bun’s JavaScript runtime inside the transpiler, during the visiting phase — before plugins and before the AST is generated. Key behaviors:
  • Macros execute in the order their imports appear
  • The transpiler waits for each macro to finish (including async macros)
  • Bun’s bundler is multi-threaded: macros execute in parallel across worker threads
  • Macros run in a fully sandboxed Bun environment with access to all Bun and Node.js APIs

Security

Macros must be explicitly imported with with { type: "macro" } before they can run. Unused macro imports have no effect. Disable macros entirely with --no-macros:
Macros cannot run from node_modules. If a package tries to invoke a macro internally, the build fails:
Your code can still import a macro from a package and invoke it:

Publishing macros in npm packages

Use the "macro" export condition to ship a macro-specific version of your package:
package.json
Users can then import the same package at runtime or as a macro using the same specifier: