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.

terminal
bun check
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.

✓ No type errors in 2 files [14.00ms]

To type check and then run, build, or test in one command, use the --check flag.

terminal
bun --check src/index.ts

Set up a project#

bun init does all of this for you. In an existing project, follow these steps.

Install Bun's type declarations

terminal
bun add -d @types/bun

@types/bun declares what Bun provides: console, fetch, Bun, and modules such as bun:test.

List them in tsconfig.json

tsconfig.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"].

Add a script

package.json
{
  "scripts": {
    "typecheck": "bun check"
  }
}

You don't need a tsconfig.json. Without one, bun check uses default compiler options 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.

Replace tsc#

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

Instead ofRun
tsc --noEmitbun check
tsc --noEmit -p packages/serverbun check -p packages/server
tsc -bbun check
tsc --noEmit && bun src/index.tsbun --check src/index.ts
tsc --noEmit && bun testbun test --check
tsc --noEmit && bun build ./app.tsbun 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:

DefaultWhat you seeWhat to do
strict is onNew errors such as TS7006: Parameter 'x' implicitly has an 'any' typeFix the errors, or set "strict": false
types is emptyCannot find name 'process', 'describe', 'Bun'List the packages, for example "types": ["node"]
Side-effect imports are checkedTS2882 on import "./index.css"Declare the file type

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.jsonWhat 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": falseRemove it. Both are always on
tsconfig.json
{
  "compilerOptions": {
    "target": "es5", 
    "target": "es2022", 
    "moduleResolution": "node", 
    "moduleResolution": "bundler", 
    "baseUrl": ".", 
    "paths": { "@/*": ["src/*"] }, 
    "paths": { "@/*": ["./src/*"] } 
  }
}

See TypeScript 6 and 7 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#

.github/workflows/typecheck.yml
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.

::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 image, or install Bun in an earlier step.

.gitlab-ci.yml
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.

.github/workflows/test.yml
- run: bun test --check

Check before you commit#

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

.git/hooks/pre-commit
#!/bin/sh
bun check
terminal
chmod +x .git/hooks/pre-commit

With lint-staged, check only the files you staged and the files they import:

package.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.

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.

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.

terminal
bun check

Another project#

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

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.

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 writes, without the three optional strictness rules noUncheckedIndexedAccess, noImplicitOverride, and noFallthroughCasesInSwitch.

{
  "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.

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:

terminal
bun check packages/web

To run the typecheck script of every workspace package, use --filter:

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.

terminal
bun --check src/index.ts
bun build --check src/index.ts --outdir out
bun test --check
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 runBun checks
A TypeScript or JavaScript fileThe file and everything it imports
A file without an extension, -e, -p, or stdinThe code and everything it imports
bun testThe test files and everything they import
bun buildThe entry points and everything they import
An HTML fileThe local scripts in <script src> and their imports
A file that imports an HTML fileAlso the local scripts of that page
A package.json scriptThe whole project, like bun check
An executable from node_modules/.bin or your PATHThe whole project
A shell scriptThe whole project
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, 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, 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.

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:

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

build.ts
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}`);
}
/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.

package.json
{
  "scripts": {
    "check": "biome check"
  }
}
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.

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.

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

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.

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.

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.

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.

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

terminal
bun add -d @types/bun
tsconfig.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.

tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}

Option 'baseUrl' has been removed#

TypeScript 7 removed the option. See Coming from TypeScript 5 for what to use instead.

Stopped before type checking#

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

bun-env.d.ts
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.

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:

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

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.

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

bun check [flags] [...files or directories]
FlagDescription
-p, --project <path>Path to a tsconfig.json or its directory
--prettyShow source code around each error. Default in a terminal
--no-prettyOne line per error, like tsc --pretty false. Default when piped
--allShow every error. Without it, identical errors are grouped above 50
--threads <n>Number of threads. Default: one per CPU core
--timingPrint how long loading and checking took
--cwd <path>Set the working directory
--strict, --target…Any compiler option, as for tsc. Overrides tsconfig.json
-h, --helpPrint the help menu