> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Macros

> Run JavaScript functions at bundle time and inline their return values directly into your bundle — useful for build-time constants, data fetching, and code generation.

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:

```typescript random.ts theme={null}
export function random() {
  return Math.random();
}
```

Use it as a macro with the `with { type: "macro" }` import attribute:

```typescript app.ts theme={null}
import { random } from "./random.ts" with { type: "macro" };

console.log(`Your random number is ${random()}`);
```

Build it:

```bash theme={null}
bun build ./app.ts
```

The macro runs at bundle time. The output contains the result, not the function:

```javascript theme={null}
// Output
console.log(`Your random number is ${0.6805550949689833}`);
```

The source code of `random.ts` does not appear in the bundle at all.

<Note>
  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.
</Note>

## 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

```typescript getGitHash.ts theme={null}
export function getGitHash() {
  const { stdout } = Bun.spawnSync({
    cmd: ["git", "rev-parse", "HEAD"],
    stdout: "pipe",
  });
  return stdout.toString().trim();
}
```

<CodeGroup>
  ```typescript input theme={null}
  import { getGitHash } from "./getGitHash.ts" with { type: "macro" };

  console.log(`Version: ${getGitHash()}`);
  ```

  ```javascript output theme={null}
  console.log(`Version: ${"3ee3259104e4507cf62c160f0ff5357ec4c7a7f8"}`);
  ```
</CodeGroup>

### 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:

```typescript meta.ts theme={null}
export async function extractMetaTags(url: string) {
  const response = await fetch(url);
  const meta: Record<string, string> = { title: "" };

  new HTMLRewriter()
    .on("title", {
      text(el) { meta.title += el.text; },
    })
    .on("meta", {
      element(el) {
        const name = el.getAttribute("name") || el.getAttribute("property");
        if (name) meta[name] = el.getAttribute("content") ?? "";
      },
    })
    .transform(response);

  return meta;
}
```

<CodeGroup>
  ```tsx input theme={null}
  import { extractMetaTags } from "./meta.ts" with { type: "macro" };

  export const Head = () => {
    const tags = extractMetaTags("https://example.com");

    if (tags.title !== "Example Domain") {
      throw new Error("Unexpected title");
    }

    return (
      <head>
        <title>{tags.title}</title>
        <meta name="viewport" content={tags.viewport} />
      </head>
    );
  };
  ```

  ```tsx output theme={null}
  export const Head = () => {
    const tags = {
      title: "Example Domain",
      viewport: "width=device-width, initial-scale=1",
    };

    return (
      <head>
        <title>{tags.title}</title>
        <meta name="viewport" content={tags.viewport} />
      </head>
    );
  };
  ```
</CodeGroup>

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:

| Return type                                              | Serialization                                      |
| -------------------------------------------------------- | -------------------------------------------------- |
| `string`, `number`, `boolean`, `null`, `object`, `array` | Inlined as-is                                      |
| `Promise`                                                | Awaited automatically; result must be serializable |
| `Response` with `application/json`                       | Parsed to object                                   |
| `Response` with `text/plain`                             | Inlined as string                                  |
| `Response` with other type                               | Base64-encoded string                              |
| `Blob`                                                   | Depends on `type` property (same as `Response`)    |
| `TypedArray` (e.g. `Uint8Array`)                         | Base64-encoded string                              |
| `function`, class instance                               | **Not supported** — throws an error                |

Macros can be async. The transpiler awaits the returned promise automatically:

```typescript theme={null}
export async function getConfig() {
  const text = await Bun.file("./config.json").text();
  return JSON.parse(text);
}
```

## Arguments

Macro arguments must be statically known at bundle time. Dynamic values are not allowed:

```typescript theme={null}
import { getText } from "./getText.ts" with { type: "macro" };

// This is NOT allowed — foo is dynamic
const foo = Math.random() ? "foo" : "bar";
const text = getText(`https://example.com/${foo}`);
```

If the value is statically known — for example, a constant or the result of another macro — it is allowed:

<CodeGroup>
  ```typescript input theme={null}
  import { getText } from "./getText.ts" with { type: "macro" };
  import { getBaseUrl } from "./getBaseUrl.ts" with { type: "macro" };

  // getBaseUrl() is statically known (runs at bundle time)
  const text = getText(`${getBaseUrl()}/content`);
  console.log("Length:", text.length);
  ```

  ```javascript output theme={null}
  console.log("Length:", 1322);
  ```
</CodeGroup>

## 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):

```typescript returnFalse.ts theme={null}
export function returnFalse() {
  return false;
}
```

```typescript index.ts theme={null}
import { returnFalse } from "./returnFalse.ts" with { type: "macro" };

if (returnFalse()) {
  console.log("This is eliminated");
}
```

```bash theme={null}
bun build ./index.ts --minify-syntax
# Output: (empty bundle)
```

## 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`:

```bash theme={null}
bun build ./index.ts --no-macros
```

```
error: Macros are disabled

foo();
^
./hello.js:3:1
```

**Macros cannot run from `node_modules`**. If a package tries to invoke a macro internally, the build fails:

```
error: For security reasons, macros cannot be run from node_modules.
```

Your code can still import a macro from a package and invoke it:

```typescript theme={null}
import { macro } from "some-package" with { type: "macro" };
macro(); // allowed — your code is invoking it
```

## Publishing macros in npm packages

Use the `"macro"` export condition to ship a macro-specific version of your package:

```json package.json theme={null}
{
  "name": "my-package",
  "exports": {
    "import": "./index.js",
    "macro": "./index.macro.js"
  }
}
```

Users can then import the same package at runtime or as a macro using the same specifier:

```typescript theme={null}
import pkg from "my-package";                               // runtime import → index.js
import { build } from "my-package" with { type: "macro" }; // macro import → index.macro.js
```
