Bun supports both ES modules (import/export) and CommonJS (require/module.exports) simultaneously. You can use either syntax in any file, and they interoperate without configuration.
Syntax
Consider the following two files:
import { hello } from "./hello";
hello();
Running index.ts prints “Hello world!”:
The import path ./hello has no extension. Extensions are optional but supported. Bun checks for the following files in order when resolving an extensionless path:
./hello.tsx
./hello.jsx
./hello.ts
./hello.mjs
./hello.js
./hello.cjs
./hello.json
./hello/index.tsx
./hello/index.jsx
./hello/index.ts
./hello/index.mjs
./hello/index.js
./hello/index.cjs
./hello/index.json
If an extension is present, Bun only looks for a file with that exact extension.
TypeScript .js extension compatibility
If you import from "*.js" or "*.jsx", Bun additionally checks for a matching *.ts or *.tsx file. This is compatible with TypeScript’s ES module support:
import { hello } from "./hello.js"; // resolves hello.ts if hello.js is absent
Module systems
Bun has native support for both CommonJS and ES modules.
Using require()
You can require() any file or package, including .ts and .mjs files:
const { foo } = require("./foo");
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");
When you require() an ES module, Bun returns the module namespace object (equivalent to import * as). When you require() a CommonJS module, it returns the module.exports object.
| Module type | require() | import * as |
|---|
| ES module | Module namespace | Module namespace |
| CommonJS | module.exports | default is module.exports; named keys are re-exported |
Using import
You can import from any file or package, including .cjs files:
import { foo } from "./foo";
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";
Mixing import and require()
In Bun, you can use both in the same file:
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";
const myStuff = require("./my-commonjs.cjs");
You cannot require() a file that uses top-level await, since require() is synchronous. Use import or dynamic import() in that case.
Importing packages
Bun implements the Node.js module resolution algorithm. Import packages from node_modules using bare specifiers:
import { stuff } from "foo";
Bun scans up the file system for a node_modules directory containing the package foo.
NODE_PATH
Bun supports NODE_PATH for additional module search directories:
NODE_PATH=./packages bun run src/index.js
Multiple paths are separated by : on Unix/macOS and ; on Windows:
NODE_PATH=./packages:./lib bun run src/index.js
Package exports field
When Bun finds a package, it reads its package.json to determine the entrypoint. It checks the exports field for the following conditions in order:
{
"name": "foo",
"exports": {
"bun": "./index.ts",
"node": "./index.js",
"require": "./index.js",
"import": "./index.mjs",
"default": "./index.js"
}
}
The "bun" export condition lets you ship TypeScript source directly to npm. If you specify a *.ts entrypoint in the "bun" condition, Bun will import and execute your TypeScript files without transpilation.
Bun respects subpath exports and imports:
{
"name": "foo",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
}
}
}
Specifying any subpath in exports prevents other subpaths from being imported directly. Only explicitly exported paths are accessible.
If exports is not defined, Bun falls back to the "module" field (ESM only), then "main":
{
"name": "foo",
"module": "./index.js",
"main": "./index.js"
}
Custom export conditions
Use --conditions to specify custom conditions when resolving exports:
bun --conditions="react-server" ./app/foo/route.js
Path re-mapping
tsconfig.json paths
Bun supports import path aliases via TypeScript’s compilerOptions.paths:
{
"compilerOptions": {
"paths": {
"config": ["./config.ts"],
"components/*": ["components/*"]
}
}
}
Non-TypeScript projects can use a jsconfig.json with the same structure.
package.json imports field
Bun supports Node.js-style subpath imports via the "imports" field in package.json. These paths must start with #:
{
"imports": {
"#config": "./config.ts",
"#components/*": "./components/*"
}
}
Both tsconfig.json paths and package.json imports can be used together.
The import.meta object gives a module access to information about itself. Bun implements the following properties:
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 the entry point
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. Equivalent to __dirname. |
import.meta.dirname | Alias for import.meta.dir (Node.js compatibility). |
import.meta.env | Alias for process.env. |
import.meta.file | The name of the current file, e.g. index.tsx. |
import.meta.path | Absolute path to the current file. Equivalent to __filename. |
import.meta.filename | Alias for import.meta.path (Node.js compatibility). |
import.meta.main | true if this file is the entry point to the current bun process, false otherwise. |
import.meta.resolve | Resolves a module specifier to a file:// URL. Equivalent to import.meta.resolve in browsers. |
import.meta.url | A file:// URL string for the current file. |