# Type checking

> Type check your TypeScript project with bun check

`bun check` type checks your TypeScript project. It reads your `tsconfig.json`, reports the same errors as `tsc` from TypeScript 7, and uses every CPU core.

```bash terminal icon="terminal"
bun check
```

```txt
src/index.ts(3,25): error TS2322: Type 'string' is not assignable to type 'number'.
Found 1 error in 1 file, checked 2 files [14.00ms]
```

`bun check` exits with code `1` if it finds an error and `0` if it finds none. It never writes files.

```txt
✓ No type errors in 2 files [14.00ms]
```

To type check and then run, build, or test in one command, use the [`--check` flag](#check-before-you-run-build-or-test).

```bash terminal icon="terminal"
bun --check src/index.ts
```

## Set up a project

[`bun init`](/runtime/templating/init) does all of this for you. In an existing project, follow these steps.

<Steps>
  <Step title="Install Bun's type declarations">

    ```bash terminal icon="terminal"
    bun add -d @types/bun
    ```

    [`@types/bun`](/typescript) declares what Bun provides: `console`, `fetch`, `Bun`, and modules such as `bun:test`.

  </Step>
  <Step title="List them in tsconfig.json">

    ```json tsconfig.json icon="file-json"
    {
      "compilerOptions": {
        "types": ["bun"]
      }
    }
    ```

    Since TypeScript 6.0, a package in `node_modules/@types` counts only if `types` lists it. List every package your code relies on, for example `["bun", "react"]`.

  </Step>
  <Step title="Add a script">

    ```json package.json icon="file-json"
    {
      "scripts": {
        "typecheck": "bun check"
      }
    }
    ```

  </Step>
</Steps>

You don't need a `tsconfig.json`. Without one, `bun check` uses [default compiler options](#without-a-tsconfigjson) and includes `@types/bun` if you have installed it.

You don't need the `typescript` package either. The declarations of `Array`, `Promise`, the DOM, and the rest of TypeScript's `lib.*.d.ts` files are built into Bun.

Your editor doesn't use `bun check`. It runs its own copy of TypeScript. To keep the two in agreement, [install the version of TypeScript that `bun check` matches](#bun-check-and-my-editor-disagree).

## Replace `tsc`

`bun check` reads the same `tsconfig.json` as `tsc`, so most projects only change the command.

| Instead of                           | Run                            |
| ------------------------------------ | ------------------------------ |
| `tsc --noEmit`                       | `bun check`                    |
| `tsc --noEmit -p packages/server`    | `bun check -p packages/server` |
| `tsc -b`                             | `bun check`                    |
| `tsc --noEmit && bun src/index.ts`   | `bun --check src/index.ts`     |
| `tsc --noEmit && bun test`           | `bun test --check`             |
| `tsc --noEmit && bun build ./app.ts` | `bun build --check ./app.ts`   |

Keep `tsc` to generate `.d.ts` files. `bun check` only checks.

### Coming from TypeScript 5

`bun check` behaves like TypeScript 7, whichever version of `typescript` you have installed. TypeScript 6 and 7 changed some defaults and removed some compiler options.

Three defaults changed:

| Default                         | What you see                                                            | What to do                                                       |
| ------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `strict` is on                  | New errors such as `TS7006: Parameter 'x' implicitly has an 'any' type` | Fix the errors, or set `"strict": false`                         |
| `types` is empty                | `Cannot find name 'process'`, `'describe'`, `'Bun'`                     | List the packages, for example `"types": ["node"]`               |
| Side-effect imports are checked | `TS2882` on `import "./index.css"`                                      | [Declare the file type](#cannot-find-module-logosvg-or-indexcss) |

If `tsconfig.json` sets a removed option, `bun check` reports that option and checks no files until you fix it. `tsc` does the same.

| In `tsconfig.json`                                    | What to do                                                                      |
| ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| `"moduleResolution": "node"`, `"node10"`, `"classic"` | Use `"bundler"`. For code that Node.js runs without a bundler, use `"nodenext"` |
| `"baseUrl": "."`                                      | Remove it, and start each entry in `paths` with `./`                            |
| `"target": "es5"`                                     | Use `"es2015"` or later                                                         |
| `"module": "amd"`, `"umd"`, `"system"`                | Use `"preserve"`, `"nodenext"`, or `"commonjs"`                                 |
| `"downlevelIteration"`, `"outFile"`                   | Remove it                                                                       |
| `"esModuleInterop": false`, `"alwaysStrict": false`   | Remove it. Both are always on                                                   |

```json tsconfig.json icon="file-json"
{
  "compilerOptions": {
    "target": "es5", // [!code --]
    "target": "es2022", // [!code ++]
    "moduleResolution": "node", // [!code --]
    "moduleResolution": "bundler", // [!code ++]
    "baseUrl": ".", // [!code --]
    "paths": { "@/*": ["src/*"] }, // [!code --]
    "paths": { "@/*": ["./src/*"] } // [!code ++]
  }
}
```

See [TypeScript 6 and 7](/typescript-6) for the `tsconfig.json` that Bun recommends.

## Run in CI

Install your dependencies, then run `bun check`. A type error fails the job.

### GitHub Actions

```yaml .github/workflows/typecheck.yml icon="file-code"
name: Type check
on: [push, pull_request]

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - run: bun check
```

In GitHub Actions, `bun check` also prints a workflow command for each error. GitHub shows those errors as annotations on the lines of the pull request diff. You don't need a problem matcher.

```txt
::error file=src/index.ts,line=3,col=25,endLine=3,endColumn=27,title=TS2322::Type 'string' is not assignable to type 'number'.
```

### GitLab CI and other providers

Use the [`oven/bun`](https://hub.docker.com/r/oven/bun) image, or install Bun in an earlier step.

```yaml .gitlab-ci.yml icon="file-code"
typecheck:
  image: oven/bun:latest
  script:
    - bun install --frozen-lockfile
    - bun check
```

### What to expect in CI

- **One line per error.** When the output is not a terminal, `bun check` prints errors in the format of `tsc --pretty false`. Problem matchers and scripts written for `tsc` keep working.
- **Errors go to stdout.** The summary line goes to stderr.
- **No cache to save or restore.** `bun check` does not write `.tsbuildinfo` files. Every run checks the whole project.
- **An empty run fails.** If there is nothing to check, `bun check` exits with code `1`. A job that runs in the wrong directory fails instead of passing.
- **Shared runners.** `bun check` starts one thread per CPU core. Use `--threads 4` to use fewer.

To check types and run tests in one step, use `bun test --check`. Bun runs the tests only if the test files, and everything they import, have no type errors.

```yaml .github/workflows/test.yml icon="file-code"
- run: bun test --check
```

## Check before you commit

Run `bun check` from a Git hook to catch type errors before they reach CI.

```bash .git/hooks/pre-commit icon="terminal"
#!/bin/sh
bun check
```

```bash terminal icon="terminal"
chmod +x .git/hooks/pre-commit
```

With [lint-staged](https://github.com/lint-staged/lint-staged), check only the files you staged and the files they import:

```json package.json icon="file-json"
{
  "lint-staged": {
    "*.{ts,tsx}": "bun check"
  }
}
```

lint-staged adds the staged file names to the command, as in `bun check src/a.ts src/b.ts`. `bun check` still uses your `tsconfig.json` for those files. `tsc` does not load `tsconfig.json` when you pass it file names.

<Note>
  A change to one file can break a file that imports it. Checking only staged files misses that error, so run `bun
  check` on the whole project in CI as well.
</Note>

## Choose what to check

### The whole project

Without arguments, `bun check` looks for `tsconfig.json` in the current directory, then in each parent directory. It checks the files that `files`, `include`, and `exclude` select.

```bash terminal icon="terminal"
bun check
```

### Another project

Use `-p` with a `tsconfig.json` or the directory that contains one.

```bash terminal icon="terminal"
bun check -p packages/server
bun check -p tsconfig.test.json
```

### Some files or directories

Pass files or directories to check only those files and what they import.

```bash terminal icon="terminal"
bun check src/index.ts
bun check packages/server packages/shared
```

Bun checks each file with the same `tsconfig.json` your editor uses for it:

- Bun uses the nearest `tsconfig.json`.
- If that `tsconfig.json` does not include the file but has `references`, Bun uses the referenced project that includes the file. The `tsconfig.json` from `create vite` works this way.
- If no project includes the file, Bun still checks it, with the options of the nearest `tsconfig.json`.

A directory means the files of the project inside that directory, so `bun check .` is the same as `bun check`. If the project has no files there, Bun checks every file in the directory that `exclude` does not rule out. An example is `bun check scripts` when `include` is `["src"]`.

### Without a `tsconfig.json`

Bun uses the compiler options that [`bun init`](/runtime/templating/init) writes, without the three optional strictness rules `noUncheckedIndexedAccess`, `noImplicitOverride`, and `noFallthroughCasesInSwitch`.

```json
{
  "lib": ["ESNext"],
  "target": "ESNext",
  "module": "Preserve",
  "moduleDetection": "force",
  "jsx": "react-jsx",
  "allowJs": true,
  "moduleResolution": "bundler",
  "allowImportingTsExtensions": true,
  "verbatimModuleSyntax": true,
  "noEmit": true,
  "strict": true,
  "skipLibCheck": true
}
```

## Monorepos

Run `bun check` once at the root. How Bun finds the projects depends on the root `tsconfig.json`.

**With `references` in the root `tsconfig.json`**, `bun check` follows them, like `tsc -b`. Bun checks each project with its own options, and prints the errors one project at a time, in build order. If two projects include the same file, Bun checks it in both and prints its errors once for each project.

Bun reads a referenced project from its source files. You don't have to build it first, and Bun does not write `.d.ts` or `.tsbuildinfo` files.

**Without a root `tsconfig.json`**, `bun check` checks every TypeScript file below the current directory. Bun checks each file once, with the nearest `tsconfig.json`. Files with no `tsconfig.json` get the [default compiler options](#without-a-tsconfigjson).

```txt
packages/web/src/index.ts(1,14): error TS2322: Type 'number' is not assignable to type 'string'.
Found 1 error in 1 file, checked 2 files across 2 projects [21.00ms]
```

To check one package, name it:

```bash terminal icon="terminal"
bun check packages/web
```

To run the `typecheck` script of every workspace package, use [`--filter`](/pm/filter):

```bash terminal icon="terminal"
bun --filter '*' typecheck
```

## Check before you run, build, or test

The `--check` flag type checks your code first. If there is a type error, Bun prints it, exits with code `1`, and does nothing else.

```bash terminal icon="terminal"
bun --check src/index.ts
bun build --check src/index.ts --outdir out
bun test --check
```

```txt
src/index.ts(3,21): error TS2322: Type 'string' is not assignable to type 'number'.
Found 1 error in 1 file, checked 2 files [14.00ms]
```

### What `--check` covers

What Bun checks depends on what you run.

| You run                                               | Bun checks                                            |
| ----------------------------------------------------- | ----------------------------------------------------- |
| A TypeScript or JavaScript file                       | The file and everything it imports                    |
| A file without an extension, `-e`, `-p`, or stdin     | The code and everything it imports                    |
| `bun test`                                            | The test files and everything they import             |
| `bun build`                                           | The entry points and everything they import           |
| An HTML file                                          | The local scripts in `<script src>` and their imports |
| A file that imports an HTML file                      | Also the local scripts of that page                   |
| A `package.json` script                               | The whole project, like `bun check`                   |
| An executable from `node_modules/.bin` or your `PATH` | The whole project                                     |
| A shell script                                        | The whole project                                     |

```bash terminal icon="terminal"
bun run --check dev
bun --check vite build
bun --check ./scripts/deploy.sh
bun --check index.html about.html
```

`--check` follows static imports. It does not cover a file that your code loads in another way:

- a worker, as in `new Worker("./worker.ts")`
- `import(name)`, where the name is computed
- `require()` in a TypeScript file
- a process that your code starts
- the files that `--preload`, or `preload` in [`bunfig.toml`](/runtime/bunfig), loads before the entry point

`bun check` covers all of these if your `tsconfig.json` includes them.

`bun --check index.html` checks once, before the development server starts. The server does not check again when you edit a file.

A few more details:

- `--filter`, `--parallel`, and `--sequential` check the whole project before the first script starts.
- Bun reads a JavaScript entry point to find what it imports, with or without `allowJs`. Bun reports errors in the JavaScript file itself only if `checkJs` is on.
- Bun runs a file without an extension as TSX, for example a script that starts with `#!/usr/bin/env bun`. The same goes for `-e`, `-p`, and code from stdin. `--check` checks that code as TSX too. Errors in it appear at `[eval]` or `[stdin]`.
- A page can have a `tsconfig.json` of its own, for example in `web/`. Bun checks the scripts of the page with that one.
- With `--tsconfig-override`, or `tsconfig` in `Bun.build()`, the check uses that `tsconfig.json`.
- With `--conditions`, the check resolves packages with those conditions too.
- With `--loader`, the check reads your files the way Bun does. For example, `--loader .js:ts` makes every `.js` file of your project TypeScript. The files in `node_modules` stay what their names say.

### Watch mode

With [`--watch`](/runtime/watch-mode), Bun checks again before every restart. After a type error, Bun waits for the next change to any file in the program, including files that contain only types.

```bash terminal icon="terminal"
bun --watch --check src/index.ts
bun test --watch --check
```

`--hot --check` restarts the process like `--watch`, so that Bun checks the code again before it runs.

If you save a file while a check is running, Bun stops that check and starts again with the new contents.

### `bun build`

`bun build --check` runs the check after the bundler has read every file and before it writes any output. The type checker gets the files from the bundler, so Bun reads each file from disk once. Bun prints a type error like any other build error:

```txt
3 | console.log(greet({ age: "36" }));
                        ^
error: TS2322: Type 'string' is not assignable to type 'number'.
    at /app/src/index.ts:3:21

1 | export function greet(user: { name?: string; age: number }) {
                                                 ^
note: TS6500: The expected type comes from property 'age' which is declared here on type '{ name?: string | undefined; age: number; }'
   at /app/src/greet.ts:1:46
```

### `Bun.build`

Pass `check: true` to [`Bun.build`](/bundler). A type error fails the build like any other build error. Each one is a `BuildMessage` whose message starts with the TypeScript error code.

The check uses the `conditions` and `loader` options of the build.

```ts build.ts icon="/icons/typescript.svg"
const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./out",
  check: true,
  throw: false,
});

for (const log of result.logs) {
  console.error(`${log.position?.file}:${log.position?.line}: ${log.message}`);
}
```

```txt
/app/src/index.ts:3: TS2322: Type 'string' is not assignable to type 'number'.
```

## If `package.json` has a `check` script

Your script wins. `bun check` runs it, like `bun run check`. To run the type checker in that project, use `bun --check`.

```json package.json icon="file-json"
{
  "scripts": {
    "check": "biome check"
  }
}
```

```bash terminal icon="terminal"
bun check   # runs "biome check"
bun --check # type checks the project
```

Inside the `check` script, and in any script it starts, `bun check` is the type checker. That makes `"check": "bun check"` work.

<Warning>
  A version of Bun that has no type checker runs `"check": "bun check"` again and again, without end. That includes an
  older `bun` in `node_modules/.bin`, which scripts find first. If not everyone on your team has upgraded, name the
  script `typecheck`.
</Warning>

With `--filter` or `--workspaces`, `check` always means the `check` script of each package.

```bash terminal icon="terminal"
bun --filter '*' check # runs the "check" script of every package
```

## Output

### In a terminal

`bun check` shows the source around each error and the declarations that the error refers to.

```txt
1 | import { greet } from "./user";
2 |
3 | const message = greet({ id: "1", name: "Ada" });
                            ^
error: TS2322: Type 'string' is not assignable to type 'number'.
    at src/index.ts:3:25

2 |   id: number;
      ^
note: The expected type comes from property 'id' which is declared here on type 'User'
   at src/user.ts:2:3

Found 1 error in 1 file, checked 2 files [14.00ms]
```

### Piped or redirected

`bun check` prints one line per error, in the format of `tsc --pretty false`. Use `--pretty` or `--no-pretty` to choose a format yourself.

```txt
src/index.ts(3,25): error TS2322: Type 'string' is not assignable to type 'number'.
```

### More than 50 errors

In a terminal, `bun check` shows each distinct error once. Below it, `bun check` prints how many times the error occurs and in which files. The most frequent error comes first.

```txt
1 | export const v0: number = "0";
                 ^
error: TS2322: Type 'string' is not assignable to type 'number'.
    at m1.ts:1:14
    60 times in 2 files
      30  m1.ts:1
      30  m2.ts:1
```

Use `--all` to show every error. The one-line format always shows every error.

### AI agents

When `AGENT=1`, `CLAUDECODE=1`, or `REPL_ID=1` is set, `bun check` prints each error as a tagged block with the source lines and no colors.

```txt
<error file="src/index.ts" line="3" column="25" code="TS2322">
Type 'string' is not assignable to type 'number'.
<source>
1 | import { greet } from "./user";
2 |
3 | const message = greet({ id: "1", name: "Ada" });
                            ^^
4 | export default message;
</source>
<related file="src/user.ts" line="2" column="3">The expected type comes from property 'id' which is declared here on type 'User'</related>
</error>
```

## Troubleshooting

### Cannot find name 'console', 'Bun', or module 'bun:test'

Bun's type declarations are missing. Install them, then add `"bun"` to `types` in `tsconfig.json`.

```bash terminal icon="terminal"
bun add -d @types/bun
```

```json tsconfig.json icon="file-json"
{
  "compilerOptions": {
    "types": ["bun"]
  }
}
```

### Cannot find name 'process', 'describe', or 'expect'

The package that declares the name is installed, but `types` does not list it. TypeScript 5 included every package in `node_modules/@types`. TypeScript 6 and 7 include only the ones you list.

```json tsconfig.json icon="file-json"
{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}
```

### Option 'baseUrl' has been removed

TypeScript 7 removed the option. See [Coming from TypeScript 5](#coming-from-typescript-5) for what to use instead.

### Stopped before type checking

```txt
tsconfig.json(7,5): error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
  Use '"paths": {"*": ["./*"]}' instead.
note: Stopped before type checking 2 files. Fix the errors above to see the rest.
Found 1 error in 1 file, checked 0 files [14.00ms]
```

A syntax error, an invalid compiler option, or a missing global type stops `bun check` before it checks any types, like `tsc`. The errors it prints are not all of the errors in your project. Fix them and run `bun check` again.

With [project references](#monorepos), each project stops on its own, and the other projects are still checked.

### Cannot find module './logo.svg' or './index.css'

Bun can import these files, but TypeScript needs a declaration for each file type. Add a `.d.ts` file to your project. The React templates of `bun init` include this one:

```ts bun-env.d.ts icon="/icons/typescript.svg"
declare module "*.svg" {
  const path: `${string}.svg`;
  export = path;
}

declare module "*.css" {}

declare module "*.module.css" {
  const classes: { readonly [key: string]: string };
  export = classes;
}
```

`@types/bun` already declares `.txt`, `.toml`, and `.html` imports.

### `bun check` runs a script instead of the type checker

Your `package.json` has a `check` script. Use `bun --check`. See [If `package.json` has a `check` script](#if-packagejson-has-a-check-script).

### `bun check` and my editor disagree

Your editor runs its own copy of TypeScript: the `typescript` package in your project, or a version that ships with the editor. `bun check` matches one exact version of TypeScript and uses that version's `lib.*.d.ts` files, whichever version you have installed. `process.versions.typescript` is that version.

Install the same version so that both follow the same rules:

```bash terminal icon="terminal"
bun add -d typescript@$(bun -p process.versions.typescript)
```

Then tell your editor to use the version in your project. In VS Code, run **TypeScript: Select TypeScript Version** from the Command Palette and choose **Use Workspace Version**.

Run the command again after you upgrade Bun, because a new version of Bun can match a newer TypeScript.

If `bun check` and `tsc` from TypeScript 7 report different errors, that is a bug in Bun. [Open an issue on GitHub](https://github.com/oven-sh/bun/issues) with code that shows the difference.

## Compiler options as flags

Every compiler option works as a flag, as it does for `tsc`. A flag overrides `tsconfig.json`, including in referenced projects. Use a flag to try a stricter option before you commit to it.

```bash terminal icon="terminal"
bun check --noUncheckedIndexedAccess
bun check --strict false
bun check --target es2022
```

Option names are not case-sensitive. `--target=es2022` is the same as `--target es2022`.

## Differences from `tsc`

`bun check` aims to report exactly what `tsc` from TypeScript 7 reports. `process.versions.typescript` is the exact version.

```bash terminal icon="terminal"
bun -p process.versions.typescript
```

These differences are intentional:

- **`bun check` only checks.** It does not write JavaScript, `.d.ts`, source map, or `.tsbuildinfo` files, even if `declaration` or `incremental` is on. Use [`bun build`](/bundler) to produce JavaScript and `tsc` to produce declaration files.
- **There is no `bun check --watch`.** To check again on every change, use [`bun --watch --check`](#watch-mode).
- **The order of files does not change the errors.** `tsc` collects the errors of a file right after it checks that file, so it drops an error that a later file raises in an earlier one. `bun check` reports it.
- **A misspelled compiler option gets a suggestion.** For `"strct": true`, `bun check` reports `TS5025: Unknown compiler option 'strct'. Did you mean 'strict'?`. `tsc` reports `TS5023: Unknown compiler option 'strct'.`
- **The `lib.*.d.ts` files are built in.** An error or a declaration in one of them has a path such as `bundled:///libs/lib.dom.d.ts`, which is not a file on your disk.
- **A note tells you when dependencies are missing.** If an import fails because a package in `package.json` is not installed, `bun check` adds a line that names the `package.json` and says how to run `bun install` for it.
- **A note tells you when type checking did not run.** If a syntax error or an invalid option stops the check, `bun check` adds a line that says [how many files it did not check](#stopped-before-type-checking).
- **Language service plugins do not load.** `bun check` ignores the `plugins` compiler option.
- **Some informational options have no effect.** `listFiles`, `listFilesOnly`, and `traceResolution` work. Others, such as `explainFiles`, do nothing.

`bun check` finds packages in `node_modules`, like `tsc`. It does not support Yarn Plug'n'Play. In a Yarn project, set `nodeLinker: node-modules` in `.yarnrc.yml`.

## CLI usage

```bash
bun check [flags] [...files or directories]
```

| Flag                     | Description                                                         |
| ------------------------ | ------------------------------------------------------------------- |
| `-p`, `--project <path>` | Path to a `tsconfig.json` or its directory                          |
| `--pretty`               | Show source code around each error. Default in a terminal           |
| `--no-pretty`            | One line per error, like `tsc --pretty false`. Default when piped   |
| `--all`                  | Show every error. Without it, identical errors are grouped above 50 |
| `--threads <n>`          | Number of threads. Default: one per CPU core                        |
| `--timing`               | Print how long loading and checking took                            |
| `--cwd <path>`           | Set the working directory                                           |
| `--strict`, `--target`…  | Any compiler option, as for `tsc`. Overrides `tsconfig.json`        |
| `-h`, `--help`           | Print the help menu                                                 |
