Introduction: Why the @types/* Question Haunts Even Experienced Developers?
Have you ever spent an hour debugging an error that ultimately boiled down to incorrectly placing an @types/* package in package.json? If so — you are not alone. The question "How do I decide whether @types/* goes into dependencies or devDependencies?" has gathered thousands of views on Stack Overflow and has become a real headache for the TypeScript community.
It seems simple, right? Throw types into devDependencies and be done with it. But it's not that simple. When your project starts growing, third-party libraries, monorepos, and CI/CD pipelines get involved, incorrect placement of types can lead to fatal build errors, unexpected crashes in production, or, even worse, a silent loss of type checking.
In this article, we will break down the top 5 most common mistakes when working with @types/*, learn how to make the right decision based on real-world scenarios, and create a checklist that will protect you from problems. Ready? Let's go!
Mistake #1: "All @types/* always go in devDependencies" — A Myth That Breaks Builds
The most popular advice on the internet goes like this: "Types are not runtime code, so they should always be in devDependencies." And this is indeed true in 90% of cases. But there is an important exception that people forget about.
When devDependencies is the right choice
If you are writing an application (an Express web server, a React frontend, a command-line utility), then feel free to put all @types/* into devDependencies. Your end user does not compile TypeScript — they run already compiled JavaScript. Types are only needed by you during development.
// Example for an Express application{ "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.11.0", "typescript": "^5.3.3" }}When devDependencies is a fatal mistake
Now imagine this: you are developing a library that exports TypeScript types for external consumers. If your library's types depend on @types/express, and you put it in devDependencies, then a user who installs your library will not automatically get those types. And their IDE will start showing red underlines.
// ❌ Incorrect for a library that exports types{ "devDependencies": { "@types/express": "^4.17.21" // The user will not see these types! }}
// ✅ Correct{ "dependencies": { "@types/express": "^4.17.21" // Types will be available to consumers }}Golden rule: If your library re-exports types from an @types/* package or uses them in its public API, then that package should be in dependencies. If the types are only used internally — devDependencies.
Mistake #2: Ignoring the Difference Between "Using in Code" and "Using in Types"
Many developers get confused: "I'm importing Request from express, so express should be in dependencies." This is true for the express package itself, but not for its types.
Let's break it down with a specific example. There are three scenarios for using the @types/lodash package:
- Scenario A: You write
import _ from 'lodash'and use_.chunk(). Types are only needed for compilation —devDependencies. - Scenario B: You create a function
function mySort(arr: T[]): T[]and use_.orderByinside. Types do not leak out —devDependencies. - Scenario C: You write
export function proc