# Module Resolution (/docs/runtime/module-resolution)

<!-- agent-signals: reading_time_min: 9 · est_tokens: 4628 · updated: 2026-07-28 -->
Related: [Watch Mode](/docs/runtime/watch-mode.md), [Debugging](/docs/runtime/debugger.md), [REPL](/docs/runtime/repl.md), [bunfig.toml](/docs/runtime/bunfig.md), [File Types](/docs/runtime/file-types.md), [JSX](/docs/runtime/jsx.md)

The JavaScript ecosystem is in a years-long transition from CommonJS modules to native ES modules (ESM), and different runtimes and build tools have historically disagreed on how import specifiers map to files on disk. Bun aims to provide a consistent and predictable module resolution system that works without configuration.

## Syntax [#syntax]

Consider the following files.

<CodeGroup>
  <CodeBlockTabs defaultValue="index.ts">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="index.ts">
        index.ts
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="hello.ts">
        hello.ts
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="index.ts">
      ```ts icon="/icons/typescript.svg" 
      import { hello } from "./hello";

      hello();
      ```
    </CodeBlockTab>

    <CodeBlockTab value="hello.ts">
      ```ts icon="/icons/typescript.svg" 
      export function hello() {
        console.log("Hello world!");
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

Running `index.ts` prints "Hello world!".

```bash icon="terminal" terminal
bun index.ts
Hello world!
```

Here `./hello` is a relative path with no extension. &#x2A;*Extensioned imports are optional but supported.** To resolve this import, Bun checks for the following files in order:

* `./hello.tsx`
* `./hello.jsx`
* `./hello.mts`
* `./hello.ts`
* `./hello.mjs`
* `./hello.js`
* `./hello.cts`
* `./hello.cjs`
* `./hello.json`
* `./hello/index.tsx`
* `./hello/index.jsx`
* `./hello/index.mts`
* `./hello/index.ts`
* `./hello/index.mjs`
* `./hello/index.js`
* `./hello/index.cts`
* `./hello/index.cjs`
* `./hello/index.json`

<Note>
  The exact order varies by context: `require()` tries CommonJS extensions (`.cts`, `.cjs`) before ESM ones (`.mts`,
  `.mjs`), and imports inside `node_modules` try JavaScript extensions before TypeScript ones. The list above shows the
  order for a local ESM `import`.
</Note>

If the import path includes an extension, Bun checks for that exact file first. If no exact match exists, Bun falls back to trying the extension list above appended to the full path (so `./hello.world` can resolve to `./hello.world.ts`).

```ts index.ts icon="/icons/typescript.svg"
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
```

There's one additional rule for TypeScript compatibility: if you import `from "*.js"` or `from "*.jsx"`, Bun also checks for a matching `*.ts` or `*.tsx` file, and outside `node_modules` `from "*.mjs"` also matches `*.mts`. This follows the TypeScript compiler's [file extension substitution](https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution), which lets source files reference each other by their compiled output paths. Note that unlike TypeScript, Bun doesn't rewrite `.cjs` to `.cts`.

```ts index.ts icon="/icons/typescript.svg"
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
import { hello } from "./hello.js"; // this also works
```

Bun supports both ES modules (`import`/`export` syntax) and CommonJS modules (`require()`/`module.exports`). The following CommonJS version also works in Bun.

<CodeGroup>
  <CodeBlockTabs defaultValue="index.js">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="index.js">
        index.js
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="hello.js">
        hello.js
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="index.js">
      ```js icon="/icons/javascript.svg" 
      const { hello } = require("./hello");

      hello();
      ```
    </CodeBlockTab>

    <CodeBlockTab value="hello.js">
      ```js icon="/icons/javascript.svg" 
      function hello() {
        console.log("Hello world!");
      }

      exports.hello = hello;
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

That said, using CommonJS is discouraged in new projects.

***

## Module systems [#module-systems]

Bun has native support for CommonJS and ES modules. ES modules are the recommended module format for new projects, but CommonJS modules are still widely used in the Node.js ecosystem.

In Bun's JavaScript runtime, both ES modules and CommonJS modules can use `require`. If the target module is an ES module, `require` returns the module namespace object (equivalent to `import * as`). If the target module is a CommonJS module, `require` returns the `module.exports` object (as in Node.js).

| Module Type | `require()`      | `import * as`                                                           |
| ----------- | ---------------- | ----------------------------------------------------------------------- |
| ES Module   | Module Namespace | Module Namespace                                                        |
| CommonJS    | module.exports   | `default` is `module.exports`, keys of module.exports are named exports |

### Using `require()` [#using-require]

You can `require()` any file or package, even `.ts` or `.mjs` files.

```ts title="index.ts" icon="/icons/typescript.svg"
const { foo } = require("./foo"); // extensions are optional
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");
```

<Accordion title="What is a CommonJS module?">
  In 2016, ECMAScript added support for [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). ES modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules.

  CommonJS modules use `module.exports` to export values and are typically imported with `require`.

  ```ts my-commonjs.cjs icon="/icons/javascript.svg"
  const stuff = require("./stuff");
  module.exports = { stuff };
  ```

  The biggest difference between CommonJS and ES modules is that CommonJS modules are synchronous, while ES modules are asynchronous. Other differences:

  * ES modules support top-level `await` and CommonJS modules don't.
  * ES modules are always in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode), while CommonJS modules are not.
  * Browsers do not have native support for CommonJS modules, but they do have native support for ES modules through `<script type="module">`.
  * CommonJS modules are not statically analyzable, while ES modules only allow static imports and exports.
  * Static `import` statements run synchronously, just like CommonJS `require`. ES modules can also be loaded on the fly with the asynchronous `import()` function, called a "dynamic import".
</Accordion>

### Using `import` [#using-import]

You can `import` any file or package, even `.cjs` files.

```ts title="index.ts" icon="/icons/typescript.svg"
import { foo } from "./foo"; // extensions are optional
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";
```

### Using `import` and `require()` together [#using-import-and-require-together]

In Bun, you can use `import` or `require` in the same file—they both work, all the time.

```ts title="index.ts" icon="/icons/typescript.svg"
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

const myStuff = require("./my-commonjs.cjs");
```

### Top level await [#top-level-await]

The only exception to this rule is top-level await. You can't `require()` a file that uses top-level await, since the `require()` function is inherently synchronous.

Fortunately, very few libraries use top-level await, so this is rarely a problem. But if you're using top-level await in your application code, make sure that file isn't `require()`'d from elsewhere in your application. Use `import` or [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) instead.

***

## Importing packages [#importing-packages]

Bun implements the Node.js module resolution algorithm, so you can import packages from `node_modules` with a bare specifier.

```ts title="index.ts" icon="/icons/typescript.svg"
import { stuff } from "foo";
```

The full algorithm is specified in the [Node.js documentation](https://nodejs.org/api/modules.html). Briefly: if you import `from "foo"`, Bun scans up the file system for a `node_modules` directory containing the package `foo`.

### NODE\_PATH [#node_path]

Bun supports `NODE_PATH` for additional module resolution directories:

```bash
NODE_PATH=./packages bun run src/index.js
```

```ts
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";
```

Multiple paths use the platform's delimiter (`:` on Unix, `;` on Windows):

```bash
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows
```

Once it finds the `foo` package, Bun reads its `package.json` to determine the package's entrypoint. Bun first reads the `exports` field and checks for the following conditions.

```json package.json icon="file-json"
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node-addons": "./index.js", // unless --no-addons was passed
    "node": "./index.js",
    "require": "./index.js", // if the importer uses require()
    "import": "./index.mjs", // if the importer uses import
    "default": "./index.js"
  }
}
```

Whichever of these conditions occurs *first* in the `package.json` determines the package's entrypoint.

Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) and [`"imports"`](https://nodejs.org/api/packages.html#imports).

```json package.json icon="file-json"
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}
```

Subpath imports and conditional imports work in conjunction with each other.

```json package.json icon="file-json"
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}
```

As in Node.js, specifying any subpath in the `"exports"` map prevents other subpaths from being importable; you can only import files that are explicitly exported. Given the preceding `package.json`:

```ts index.ts icon="/icons/typescript.svg"
import stuff from "foo"; // this works
import stuff from "foo/index.mjs"; // this doesn't
```

<Note>
  **Shipping TypeScript** — Bun supports the special `"bun"` export condition. If your library is written in TypeScript,
  you can publish your un-transpiled TypeScript files to `npm` directly. If you specify your package's `*.ts` entrypoint
  in the `"bun"` condition, Bun imports and executes your TypeScript source files directly.
</Note>

If `exports` is not defined, Bun falls back to the legacy top-level entrypoint fields. At runtime Bun prefers [`"main"`](https://nodejs.org/api/packages.html#main) (or an implicit `index.*` file) when present, and uses `"module"` otherwise.

```json package.json icon="file-json"
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}
```

### Custom conditions [#custom-conditions]

The `--conditions` flag specifies the conditions to use when resolving packages from `package.json` `"exports"`.

Both `bun build` and Bun's runtime support this flag.

```sh terminal icon="terminal"
# Use it with bun build:
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# Use it with bun's runtime:
bun --conditions="react-server" ./app/foo/route.js
```

You can also use `conditions` programmatically with `Bun.build`:

```ts build.ts icon="/icons/typescript.svg"
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});
```

***

## Path re-mapping [#path-re-mapping]

Bun supports import path re-mapping through TypeScript's [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in `tsconfig.json`, which works well with editors. If you aren't a TypeScript user, use a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root for the same behavior.

```json tsconfig.json icon="file-json"
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // map specifier to file
      "components/*": ["components/*"] // wildcard matching
    }
  }
}
```

Bun also supports [Node.js-style subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `package.json`, where mapped paths must start with `#`. TypeScript and editors resolve these too, and you can use both mechanisms together.

```json package.json icon="file-json"
{
  "imports": {
    "#config": "./config.ts", // map specifier to file
    "#components/*": "./components/*" // wildcard matching
  }
}
```

<Accordion title="Low-level details of CommonJS interop in Bun">
  Bun's JavaScript runtime has native support for CommonJS. When Bun's JavaScript transpiler detects usages of `module.exports`, it treats the file as CommonJS. The module loader then wraps the transpiled module in a function shaped like this:

  ```js
  (function (module, exports, require) {
    // transpiled module
  })(module, exports, require);
  ```

  `module`, `exports`, and `require` are very much like the `module`, `exports`, and `require` in Node.js. These are assigned through a [`with scope`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) in C++. An internal `Map` stores the `exports` object to handle cyclical `require` calls before the module is fully loaded.

  Once the CommonJS module is successfully evaluated, a Synthetic Module Record is created with the `default` ES Module [export set to `module.exports`](https://github.com/oven-sh/bun/blob/9b6913e1a674ceb7f670f917fc355bb8758c6c72/src/bun.js/bindings/CommonJSModuleRecord.cpp#L212-L213) and keys of the `module.exports` object are re-exported as named exports (if the `module.exports` object is an object).

  Bun's bundler works differently: it wraps the CommonJS module in a `require_${moduleName}` function which returns the `module.exports` object.
</Accordion>

***

## `import.meta` [#importmeta]

The `import.meta` object exposes information about the current module. It's part of the JavaScript language, but its contents are not standardized: each "host" (browser or runtime) implements its own properties on the `import.meta` object.

Bun implements the following properties.

```ts /path/to/project/file.ts
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"

import.meta.main; // `true` if this file is directly executed by `bun run`
// `false` otherwise

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
```

| Property               | Description                                                                                                                                                                                                                                                                                                                   |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `import.meta.dir`      | Absolute path to the directory containing the current file, e.g. `/path/to/project`. Equivalent to `__dirname` in CommonJS modules (and Node.js)                                                                                                                                                                              |
| `import.meta.dirname`  | An alias to `import.meta.dir`, for Node.js compatibility                                                                                                                                                                                                                                                                      |
| `import.meta.env`      | An alias to `process.env`.                                                                                                                                                                                                                                                                                                    |
| `import.meta.file`     | The name of the current file, e.g. `index.tsx`                                                                                                                                                                                                                                                                                |
| `import.meta.path`     | Absolute path to the current file, e.g. `/path/to/project/index.ts`. Equivalent to `__filename` in CommonJS modules (and Node.js)                                                                                                                                                                                             |
| `import.meta.filename` | An alias to `import.meta.path`, for Node.js compatibility                                                                                                                                                                                                                                                                     |
| `import.meta.main`     | Indicates whether the current file is the entrypoint to the current `bun` process: `true` if it's executed directly by `bun run`, `false` if it's imported                                                                                                                                                                    |
| `import.meta.resolve`  | Resolve a module specifier (e.g. `"zod"` or `"./file.tsx"`) to a url. Equivalent to [`import.meta.resolve` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve). Example: `import.meta.resolve("zod")` returns `"file:///path/to/project/node_modules/zod/index.ts"` |
| `import.meta.url`      | A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)                                                                                                       |
