Add TypeScript to a JavaScript Project
Learn how to add TypeScript to an existing JavaScript project without rewriting everything. Install TypeScript, create a tsconfig, and start getting type safety file by file.
Adding TypeScript to a JavaScript project means installing the TypeScript compiler, creating a configuration file, and letting your existing JavaScript files coexist with new TypeScript files. You do not need to rewrite the entire codebase at once. Most teams add TypeScript to a JavaScript project gradually, converting one file at a time while the rest of the app keeps working.
This guide walks through the setup for a Node.js project. The same principles apply for browser projects, React apps, or any other JavaScript environment.
Install TypeScript
TypeScript ships as a regular npm package, so adding it works the same way as installing any other tool. Install it as a development dependency, since the compiler is only needed while you build the project, not when it runs in production.
npm install --save-dev typescriptThis command downloads TypeScript into the project and gives you the compiler command, called tsc. Confirm the install worked by asking the compiler for its version number instead of guessing.
npx tsc --versionIf this prints a version number, the compiler is installed and ready to use. A Node.js project also needs type declarations for Node's built-in APIs, since TypeScript does not know about them by default. Install the companion package for Node types next.
npm install --save-dev @types/nodeThis package teaches TypeScript what globals like the process object, console, and Buffer look like, along with the rest of the Node standard library. Without it, the compiler treats those names as unknown and reports errors. Skip this step for browser-only projects; DOM types are already built into TypeScript.
Create the tsconfig.json
A configuration file named tsconfig.json tells TypeScript where your source files live and how strict the checks should be. Generate a starting version at the project root using the compiler's built-in scaffold command.
npx tsc --initThe default output from this command includes dozens of commented-out options, which is more than a JavaScript project needs on day one. Replace it with a smaller configuration that stays lenient while you get the first files compiling.
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"allowJs": true,
"strict": false
},
"include": ["src/**/*"]
}The option that matters most here is allowJs, set to true. It tells the compiler to process plain JavaScript files alongside the new TypeScript ones, so nothing in your existing codebase gets ignored or left behind during the migration.
Strict checking is turned off on purpose at this stage, since you are introducing TypeScript into code that was never written with types in mind. Start lenient, get the project compiling, then tighten the rules once the basics are in place. For details on strict mode, see Strict Mode in TypeScript.
Organize Your Directories
TypeScript expects separate input and output directories so it never overwrites your original source files when it compiles. A typical structure separates the two clearly, with source code in one folder and generated output in another.
project/
src/ # your .js and .ts source files
dist/ # compiled JavaScript output
tsconfig.jsonIf your JavaScript currently sits in the project root, move it into a folder named src first. This keeps the files you edit separate from the files the compiler generates, which avoids accidentally editing build output by mistake.
The output directory setting tells TypeScript to write compiled files into the dist folder, and the root directory setting tells it that src is where source files live. Together they make the generated output mirror the structure of your source tree.
Rename Your First File
Pick one JavaScript file to convert first. A utility file with few dependencies is a good starting point, since a mistake there is unlikely to ripple through the rest of the app. Rename its extension from .js to .ts so the compiler starts checking it.
mv src/utils.js src/utils.tsWith the file renamed, run the compiler to see how it holds up against TypeScript's checks for the first time.
npx tscSeeing errors after this first run is normal, since the file was never type-checked before. Three kinds of error show up often during an initial conversion:
- A missing module error, when an import path resolves incorrectly.
- An implicit any error, when a function parameter has no declared type.
- A missing property error, when TypeScript inferred an empty object type for a variable.
Do not try to fix every error at once. Focus only on the file you just renamed, and for each error decide whether to add a type annotation now or temporarily relax the setting that is complaining.
Fix Common First Errors
Implicit any errors are the most common first hurdle. When a function parameter has no type, TypeScript infers it as any and, with stricter settings, flags it. Adding a short type annotation to the parameter clears the error immediately.
function formatName(name: string) {
return name.toUpperCase();
}If annotating every parameter right away is too much work for a large file, turn off the noImplicitAny check in the configuration file temporarily and come back to it later.
{
"compilerOptions": {
"noImplicitAny": false
}
}A second common error appears when you import a package and TypeScript cannot find type information for it. Install the matching types package for that library to resolve it.
npm install --save-dev @types/expressMany popular packages already bundle their own type definitions, so this step is not always necessary. For packages without types, see Type Untyped Packages in TypeScript.
A third error shows up when JavaScript code adds properties to an object dynamically, a pattern that is common in loosely typed code but conflicts with how TypeScript infers object shapes.
const config = {};
config.port = 3000;TypeScript reports that the port property does not exist on the inferred empty object type. Declaring the object's shape upfront, instead of letting TypeScript infer it from an empty literal, fixes the error.
const config: { port: number } = { port: 3000 };Add an npm Script
Typing the full compiler command every time gets tedious, so add a build script to package.json that wraps it. This also makes the build step consistent for anyone else working on the project.
{
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch"
}
}Running npm run build now compiles the project the same way npx tsc did earlier. The watch variant recompiles automatically whenever a file changes, which saves time while you are actively converting files.
At this point, the project has TypeScript installed, a working configuration, and at least one converted file compiling alongside the remaining JavaScript files.
Tighten Settings Gradually
Once the initial files compile without errors, start enabling stricter checks one at a time instead of turning them all on together. Introduce them in the order shown below, moving to the next row only after the errors from the previous one are fixed.
| Setting | What it does | When to enable |
|---|---|---|
| Flag untyped parameters | Reports function parameters without a declared type | After you have added basic types to most functions |
| Enforce null checks | Treats null and undefined as separate, trackable types | After you have handled nullable values in your code |
| Enable full strict mode | Turns on every strict check at once | When you are ready for full type safety |
Each setting surfaces a new batch of errors on its own, so enabling them together makes it hard to tell which change caused which error. Fix the errors from one setting before turning on the next.
For more on individual compiler options, see Important tsconfig Options Explained.
What About Build Tools
A bundler needs its own way of handling the new file type, either through a loader that understands it or a separate compile step that runs before bundling.
With Webpack, install a loader that lets the bundler compile files directly using your existing configuration file.
npm install --save-dev ts-loaderThen point Webpack at your entry file, add both extensions to module resolution, and tell it to run that loader on any file matching the TypeScript extension.
module.exports = {
entry: "./src/index.ts",
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{ test: /\.ts$/, use: "ts-loader", exclude: /node_modules/ },
],
},
};Vite takes a different approach: it transpiles source files on the fly for fast rebuilds but does not check types as part of that process. Run the compiler separately, with its no-emit flag, to catch type errors during development or in continuous integration.
When to Convert More Files
There is no deadline for finishing the migration. Keep the lenient setting from earlier enabled for as long as you need, and rename files only when you are ready to add types to them. Many teams keep it enabled permanently so legacy JavaScript and newer TypeScript code continue to coexist.
Each file you convert gives you better editor support and catches more bugs at compile time. The pace is entirely up to you and your team.
Rune AI
Key Insights
- Install TypeScript as a dev dependency with
npm install --save-dev typescript. - Create a tsconfig.json with
allowJs: trueto accept both .js and .ts files. - Rename one .js file to .ts at a time and fix compiler errors as they appear.
- Enable stricter checks like noImplicitAny only after the initial conversion.
- TypeScript compiles to plain JavaScript that works everywhere your old JS did.
Frequently Asked Questions
Do I have to rename all my .js files to .ts right away?
Will adding TypeScript break my existing JavaScript code?
Can I use TypeScript with Webpack or Vite?
Conclusion
Adding TypeScript to a JavaScript project is a gradual process. Install the compiler, create a tsconfig with allowJs enabled, and start renaming files from .js to .ts one at a time. Tighten compiler options as your team gets comfortable. You control the pace.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.