
If you've recently upgraded your TypeScript project, you might have encountered a surprising error message when trying to import modules without file extensions. TypeScript is now enforcing a change in how you write import statements, and while it might seem like an annoying requirement at first, there are good reasons behind this shift.
The Problem: Missing File Extensions in Imports
For years, developers have been used to writing imports like this:
import example from 'foo';
But with newer TypeScript configurations, particularly when using the 'node16' or 'nodenext' module resolution strategy, you'll encounter this error:
Relative import paths need explicit file extensions in ES imports when module resolution is node16 or nodenext.
The Incorrect Solution: Adding .ts Extensions
Your first instinct might be to add the TypeScript extension to the import path:
import example from 'foo.ts';
But this leads to another error:
An import path can only end with a .ts extension when allowImportingTsExtensions is enabled.
The Correct Solution: Adding .js Extensions
Surprisingly, the correct solution is to add a .js extension to your imports, even when you're importing from TypeScript files:
import example from 'foo.js';
This might seem counterintuitive since you're referencing a file that doesn't actually exist yet (your TypeScript file will be compiled to JavaScript later). However, this approach aligns with how the compiled code will eventually run.
Why TypeScript Requires Explicit File Extensions
There are two main reasons behind this change:
- Performance improvements in Node.js applications
- Alignment with the actual runtime environment
When Node.js encounters an import without an extension, it has to try multiple resolution strategies (checking for .js, index.js, etc.). By specifying the exact file extension, Node.js can load modules faster without this resolution overhead.

As for why .js instead of .ts extensions - remember that your TypeScript code will be compiled to JavaScript before execution. If you were to use .ts extensions in your imports, those paths wouldn't exist in the compiled output, causing runtime errors when Node.js tries to load them.
Configuration Options for File Extensions in TypeScript
If you prefer not to use explicit .js extensions in your imports, you have a few configuration options:
Option 1: Use the 'bundler' Module Resolution
In your tsconfig.json, you can change the moduleResolution setting:
{
"compilerOptions": {
"moduleResolution": "bundler"
// other options...
}
}
The 'bundler' resolution strategy is designed for projects that use bundlers like esbuild, webpack, or swc. These tools handle more complex module resolution and transformations automatically.

Option 2: Enable allowImportingTsExtensions
If you really want to use .ts extensions in your imports, you can enable the allowImportingTsExtensions option, but this comes with important caveats:
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"noEmit": true
// other options...
}
}
Note that this requires setting noEmit to true, meaning TypeScript won't generate output files. This approach is typically only useful for projects that use another tool for transpilation.
Understanding the Trade-offs
While adding .js extensions to imports may feel like an extra step, it offers several benefits:
- Faster module resolution at runtime
- Clearer mapping between source and compiled code
- More explicit dependencies that help tooling and developers understand the codebase
- Better compatibility with ESM (ECMAScript Modules)

Best Practices for TypeScript Import Extensions
Based on the current TypeScript ecosystem, here are some recommendations:
- For Node.js applications using ESM, embrace explicit .js extensions
- For frontend applications using bundlers, consider the 'bundler' moduleResolution option
- Use path aliases in tsconfig.json to reduce the verbosity of import paths
- Consider using tools like eslint-plugin-import to enforce consistent import patterns
- Document your project's import convention to maintain consistency across the team
Conclusion
While requiring explicit file extensions in TypeScript imports might seem like an unnecessary change at first, it aligns with modern JavaScript practices and improves runtime performance. By understanding the reasoning behind this change and the available configuration options, you can choose the approach that best suits your project's needs.
Whether you decide to embrace .js extensions in your imports or configure TypeScript to work without them, maintaining consistency throughout your codebase will help ensure a smoother development experience for everyone on your team.
Let's Watch!
Why TypeScript Requires File Extensions in Import Statements: The Complete Guide
Ready to enhance your neural network?
Access our quantum knowledge cores and upgrade your programming abilities.
Initialize Training Sequence