Question
I have a TypeScript package that compiles into a dist folder containing both JavaScript files and declaration files:
├── dist
│ ├── annotations.d.ts
│ ├── annotations.js
│ ├── index.d.ts
│ ├── index.js
│ ├── injector.d.ts
│ ├── injector.js
│ ├── profiler.d.ts
│ ├── profiler.js
│ ├── providers.d.ts
│ ├── providers.js
│ ├── util.d.ts
│ └── util.js
├── LICENSE
├── package.json
├── README.md
├── src
│ ├── annotations.ts
│ ├── index.ts
│ ├── injector.ts
│ ├── profiler.ts
│ ├── providers.ts
│ └── util.ts
└── tsconfig.json
In package.json, I set:
{
"main": "dist/index.js"
}
When I use the package in Node.js, it works correctly. However, in TypeScript, this import causes an error:
import { Injector } from '@ts-stack/di';
Error:
Could not find a declaration file for module '@ts-stack/di'. '/path/to/node_modules/@ts-stack/di/dist/index.js' implicitly has an 'any' type.
But if I import the file directly, TypeScript works:
import { Injector } from '/path/to/node_modules/@ts-stack/di/dist/index.js';
Why does TypeScript fail to find the declaration file when importing the package name, and how should this package be configured correctly?
Short Answer
By the end of this page, you will understand why TypeScript can run a JavaScript package but still fail to find its type declarations, how package.json fields such as main and types affect module resolution, and how to properly publish a TypeScript package so consumers can import it by package name without errors.
Concept
TypeScript resolves runtime code and type information separately.
Node.js only cares about the JavaScript entry point, usually defined by the main field in package.json:
{
"main": "dist/index.js"
}
That is enough for Node to execute your package.
TypeScript, however, also needs to know where the declaration files are. Declaration files are the .d.ts files that describe the public API of your package.
If TypeScript can load the JavaScript file but cannot find matching type declarations, it treats the module as any and shows an error like:
Could not find a declaration file for module 'package-name'
Even if dist/index.d.ts exists, TypeScript may not automatically use it when importing the package by name unless the package metadata clearly points to it.
The usual fix is to add a types field to package.json:
{
"main":
Mental Model
Think of a package like a book:
maintells Node.js which chapter to start reading.typestells TypeScript where the table of contents and glossary are.
Without types, Node can still read the book, but TypeScript cannot understand the meaning of the terms inside it.
So your package may run fine but still be invisible to the type system.
Syntax and Examples
A TypeScript-friendly package usually includes both JavaScript output and declaration files.
Correct package.json
{
"name": "@ts-stack/di",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
Basic library structure
dist/
index.js
index.d.ts
Example library code
Source:
// src/index.ts
export class Injector {
get(name: string): string {
return `Resolved: ${name}`;
}
}
Compiled output:
// dist/index.js
export class {
() {
;
}
}
Step by Step Execution
Consider this package metadata:
{
"main": "dist/index.js"
}
And this import:
import { Injector } from '@ts-stack/di';
What happens step by step
- TypeScript sees the package import
@ts-stack/di. - It looks inside that package's
package.json. - It finds
main: "dist/index.js", so it knows which JavaScript file is the runtime entry. - It then tries to find type declarations for the package.
- If there is no
typesfield and TypeScript cannot confidently resolve the declarations for the package entry, it reports that the module has no declaration file. - Because types are missing, the package is treated as
any.
Why direct file import may appear to work
import { Injector } from '/path/to/node_modules/@ts-stack/di/dist/index.js';
In this case, TypeScript is resolving a specific file path. Since sits next to , TypeScript can often match them directly.
Real World Use Cases
This issue appears often when building and publishing TypeScript libraries.
Shared internal packages
A team publishes a utility package used by several services:
import { formatCurrency } from '@company/utils';
If types is missing, every TypeScript project importing it loses autocomplete and type safety.
Open-source npm libraries
A library author publishes compiled JavaScript and .d.ts files, but forgets to include the declaration entry in package.json. JavaScript users are fine, but TypeScript users get errors.
Monorepos
In a workspace setup, one package may depend on another local package. If the dependent package is not configured with correct declaration metadata, imports work at runtime but fail in editors and builds.
SDKs and API clients
Packages that expose classes, functions, and config objects rely heavily on declaration files so users can discover the API without reading source code.
Real Codebase Usage
In real projects, developers usually combine several patterns to make package typing reliable.
Explicit package entry points
The most common setup is:
{
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
This makes the package entry predictable.
Build declarations automatically
In tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"outDir": "dist"
}
}
This ensures .d.ts files are generated whenever the package builds.
Export from a single public entry file
Developers usually re-export public APIs from src/index.ts:
Common Mistakes
1. Setting only main
Broken:
{
"main": "dist/index.js"
}
Why it fails:
- Node can run the package.
- TypeScript may not know where the declarations start.
Fix:
{
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
2. Forgetting to generate declaration files
Broken tsconfig.json:
{
"compilerOptions": {
"outDir": "dist"
}
}
Fix:
{
Comparisons
| Concept | Purpose | Used By | Example |
|---|---|---|---|
main | JavaScript runtime entry | Node.js, bundlers | "main": "dist/index.js" |
types | TypeScript declaration entry | TypeScript | "types": "dist/index.d.ts" |
typings | Older alias for types | TypeScript | "typings": "dist/index.d.ts" |
| direct file import | Import a concrete file path | Runtime and TS file resolution | import x from './dist/index.js' |
Cheat Sheet
{
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
Key rules
mainpoints to the runtime JavaScript entry.typespoints to the TypeScript declaration entry.- Generate declarations with
declaration: true. - Publish the
.d.tsfiles along with.jsfiles. - Prefer importing the package name, not internal file paths.
Minimal tsconfig.json
{
"compilerOptions": {
"declaration": true,
"outDir": "dist"
}
}
Minimal package structure
FAQ
Why does Node.js work but TypeScript fails?
Node.js only needs JavaScript to run. TypeScript also needs declaration files for type checking.
Is main enough for a TypeScript package?
No. main is for runtime code. You should also provide types for declarations.
Do I need @types/... for my own TypeScript library?
Usually no. If your package generates and publishes its own .d.ts files, that is enough.
What should the types field point to?
It should point to your package's public declaration entry, usually something like dist/index.d.ts.
Can TypeScript infer types from JavaScript automatically?
Sometimes partially, but published packages should not rely on that. Declaration files are the standard solution.
What is the difference between types and typings?
They serve the same purpose. types is the common modern field name.
Why does importing dist/index.js work?
Because TypeScript can often match that exact file with a nearby . That does not mean the package itself is configured correctly.
Mini Project
Description
Create a tiny TypeScript library called math-tools and configure it so another TypeScript file can import it by package name without any declaration errors. This demonstrates the exact packaging pattern needed for reusable libraries.
Goal
Build and configure a package with both runtime and type entry points so TypeScript consumers get correct imports and autocomplete.
Requirements
- Create a library entry file that exports at least one function or class.
- Compile the library to a
distfolder and generate declaration files. - Configure
package.jsonwith bothmainandtypes. - Write a consumer file that imports the package by name.
- Ensure the import works without using internal
dist/...paths.
Keep learning
Related questions
Angular formGroup Error Explained: Fixing 'Can't bind to formGroup' in Reactive Forms
Learn why Angular shows 'Can't bind to formGroup' and how to fix it by importing ReactiveFormsModule correctly.
Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing
Learn why TypeScript rejects string object indexing and how to fix it with keyof, unions, and typed object keys in React.
Fix "Property has no initializer" in Angular TypeScript Components
Learn why Angular TypeScript shows "Property has no initializer" and how to fix it using defaults, optional properties, or definite assignment.