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.
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.
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
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
{
"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
{
"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 and checks the same files, 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 -b |
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 |
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 |
{
"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 for the tsconfig.json that Bun recommends.
Run in CI
Install your dependencies, then run bun check. A type error fails the job.
If your projects are connected by references, run bun check -b to check all of them.
GitHub Actions
name: Type check
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aphrody-labs/bun/.github/actions/setup-bun@main
- 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.
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 checkprints errors in the format oftsc --pretty false. Problem matchers and scripts written fortsckeep working. - Errors go to stdout. The summary line goes to stderr.
- No cache to save or restore.
bun checkdoes not write.tsbuildinfofiles. Every run checks the whole project. - An empty run fails. If there is nothing to check,
bun checkexits with code1. A job that runs in the wrong directory fails instead of passing. - Shared runners.
bun checkstarts one thread per CPU core. Use--threads 4to 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.
- run: bun test --check
Check before you commit
Run bun check from a Git hook to catch type errors before they reach CI.
#!/bin/sh
bun check
chmod +x .git/hooks/pre-commit
With lint-staged, check only the files you staged and the files they import:
{
"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.
bun check
Another project
Use -p with a tsconfig.json or the directory that contains one.
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.
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.jsondoes not include the file but hasreferences, Bun uses the referenced project that includes the file. Thetsconfig.jsonfromcreate viteworks 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. 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
bun check checks the project that tsc checks in the same directory: the one with the nearest tsconfig.json.
One package
Run bun check in the package, or name it from anywhere.
bun check -p packages/web
If the package has references, Bun reads the referenced projects from their source files. You don't have to build them first, and Bun does not write .d.ts or .tsbuildinfo files. Bun reports the errors of the package only. Compiler options on the command line apply to the package only.
Every project
Use -b to follow references, like tsc -b.
bun check -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 reports its errors for both, like tsc -b.
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]
A tsconfig.json with nothing but references
The tsconfig.json from create vite has "files": [] and references. tsc without -b checks nothing there and succeeds. bun check follows the references, and says so:
note: tsconfig.json has no files of its own. Checked the projects it references, like tsc -b.
No tsconfig.json at the root
If no tsconfig.json is in the current directory or above it, but some are below it, bun check does not guess which ones you mean. It lists them and stops.
error: No tsconfig.json in '/home/me/app' or above it. Below it:
packages/server/tsconfig.json
packages/web/tsconfig.json
To check one: bun check -p packages/server/tsconfig.json
To check everything below this directory: bun check .
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.
To run the typecheck script of every workspace package, use --filter:
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.
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 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 |
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 computedrequire()in a TypeScript file- a process that your code starts
- the files that
--preload, orpreloadinbunfig.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--sequentialcheck 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 ifcheckJsis 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.--checkchecks that code as TSX too. Errors in it appear at[eval]or[stdin]. - A page can have a
tsconfig.jsonof its own, for example inweb/. Bun checks the scripts of the page with that one. - With
--tsconfig-override, ortsconfiginBun.build(), the check uses thattsconfig.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:tsmakes every.jsfile of your project TypeScript. The files innode_modulesstay 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.
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.
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.
{
"scripts": {
"check": "biome check"
}
}
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.
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.
bun add -d @types/bun
{
"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.
{
"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:
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:
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. Use a flag to try a stricter option before you commit to it.
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.
A flag applies to the projects that bun check reports. With -b, that includes every referenced project.
bun check -b --strict
Differences from tsc
bun check aims to report exactly what tsc from TypeScript 7 reports. process.versions.typescript is the exact version.
bun -p process.versions.typescript
These differences are intentional:
bun checkonly checks. It does not write JavaScript,.d.ts, source map, or.tsbuildinfofiles, even ifdeclarationorincrementalis on. Usebun buildto produce JavaScript andtscto produce declaration files.- Referenced projects don't have to be built.
tscwithout-breads the.d.tsfiles that an earlier build wrote, and reports TS6305 if there are none.bun checkreads the source files. - Compiler options work with
-b.tsc -b --strictreports TS5094.bun check -b --strictapplies the option to every project. - A
tsconfig.jsonwith nothing butreferencesis followed.tscwithout-bchecks nothing there. - Paths work next to a
tsconfig.json.tsc src/index.tsreports TS5112 if there is atsconfig.json.bun check src/index.tschecks the file with it. - There is no
bun check --watch. To check again on every change, usebun --watch --check. - The order of files does not change the errors.
tsccollects 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 checkreports it. - The
lib.*.d.tsfiles are built in. An error or a declaration in one of them has a path such asbundled:///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.jsonis not installed,bun checkadds a line that names thepackage.jsonand says how to runbun installfor it. - A note tells you when type checking did not run. If a syntax error or an invalid option stops the check,
bun checkadds a line that says how many files it did not check. - Language service plugins do not load.
bun checkignores thepluginscompiler option. - Some informational options have no effect.
listFiles,listFilesOnly,explainFiles, andtraceResolutionwork. Others, such asdiagnostics, 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]
| Flag | Description |
|---|---|
-p, --project <path> | Path to a tsconfig.json or its directory |
-b, --build | Check the projects in references too, like tsc -b |
--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 |