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.
bun checksrc/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.tsSet 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, 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 |
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",
"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#
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 checkIn 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 checkWhat 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 --checkCheck before you commit#
Run bun check from a Git hook to catch type errors before they reach CI.
#!/bin/sh
bun checkchmod +x .git/hooks/pre-commitWith 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 checkAnother project#
Use -p with a tsconfig.json or the directory that contains one.
bun check -p packages/server
bun check -p tsconfig.test.jsonSome 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/sharedBun 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, 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:
bun check packages/webTo run the typecheck script of every workspace package, use --filter:
bun --filter '*' typecheckCheck 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 --checksrc/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:46Bun.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 projectInside 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 packageOutput#
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:1Use --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, including in referenced projects. Use a flag to try a stricter option before you commit to it.
bun check --noUncheckedIndexedAccess
bun check --strict false
bun check --target es2022Option 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.
bun -p process.versions.typescriptThese 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.- 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. - A misspelled compiler option gets a suggestion. For
"strct": true,bun checkreportsTS5025: Unknown compiler option 'strct'. Did you mean 'strict'?.tscreportsTS5023: Unknown compiler option 'strct'. - 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, andtraceResolutionwork. Others, such asexplainFiles, 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 |
--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 |