Question

How to Fix ESLint import/no-unresolved with TypeScript Imports

Question

In a TypeScript file named app.spec.ts, I have this import:

import app from './app';

ESLint reports this error:

2:17  error  Unable to resolve path to module './app'  import/no-unresolved

The file ./app.ts does exist, but it has not been compiled to ./app.js yet. After compiling the TypeScript file to JavaScript, the error disappears.

Since ESLint is being used with TypeScript, I want it to resolve imports against .ts files rather than requiring .js output files.

My ESLint configuration already includes TypeScript parsing:

{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  }
}

How can I configure ESLint so that module resolution works correctly with .ts files?

For context, app.ts contains:

import bodyParser from 'body-parser';
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { buildSchema } from 'graphql';

const app = express();

const schema = buildSchema(`
    type Query {
        hello: String
    }
`);
const root = { hello: () => 'Hello world!' };

app.use(bodyParser());
app.use('/graphql', graphqlHTTP({
    schema,
    rootValue: root,
    graphiql: true,
}));

export default app;

Short Answer

By the end of this page, you will understand why ESLint can parse TypeScript but still fail to resolve TypeScript imports, and how to fix that by configuring the correct import resolver. You will also learn the difference between parsing, linting, and module resolution, plus common mistakes that cause import/no-unresolved errors in TypeScript projects.

Concept

ESLint and TypeScript solve different problems.

  • TypeScript checks types and understands .ts files.
  • ESLint checks code style and code-quality rules.
  • The rule import/no-unresolved comes from eslint-plugin-import, and it needs its own way to find imported files.

This is the key idea: setting the TypeScript parser does not automatically teach ESLint how to resolve TypeScript imports.

When you write:

import app from './app';

TypeScript knows that ./app may refer to ./app.ts. But the import/no-unresolved rule may still use Node-style resolution that mainly expects JavaScript files unless you configure a TypeScript-aware resolver.

That is why the error disappears after compilation: once app.js exists, the default resolver can find it.

To make ESLint work properly with TypeScript source files, you usually need:

  1. @typescript-eslint/parser to parse TypeScript syntax.
  2. eslint-plugin-import for import rules.
  3. eslint-import-resolver-typescript so import rules can resolve .ts and .tsx files.

This matters in real programming because unresolved import errors are common in:

  • TypeScript backends
  • React apps with .tsx
  • monorepos
  • projects using path aliases like @/utils

Without the correct resolver, ESLint can report false errors even when your TypeScript project is valid.

Mental Model

Think of ESLint as a building inspector.

  • The parser tells the inspector how to read the blueprint.
  • The rules tell the inspector what problems to look for.
  • The resolver tells the inspector where rooms and doors actually are.

In this case:

  • @typescript-eslint/parser helps ESLint read TypeScript.
  • import/no-unresolved checks whether an import path points to a real module.
  • eslint-import-resolver-typescript helps ESLint find .ts files.

If you only give ESLint the parser, it can read the file, but it may still not know where ./app points unless you also give it the right resolver.

Take Quiz

Syntax and Examples

The usual fix is to install and configure the TypeScript import resolver.

Install the needed packages

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import eslint-import-resolver-typescript

ESLint configuration

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint", "import"],
  "parserOptions": {
    "project": "./tsconfig.json",
    "sourceType": "module"
  },
  "settings": {
    "import/resolver": {
      "typescript": {
        "project": "./tsconfig.json"
      }
    }
  },
  "rules": {
    "import/no-unresolved": "error"
  }
}

Example import

import app from './app';

If app.ts exists, ESLint should now resolve it correctly without requiring app.js to exist.

Optional: include Node resolver too

Some projects use both TypeScript files and plain Node resolution. In that case:

{
  "settings": {
    "import/resolver": {
      "typescript": {
        "project": "./tsconfig.json"
      },
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  }
}

This can help when your project mixes JavaScript and TypeScript.

Example using flat config style idea

If you use a newer ESLint config format, the same principle still applies: you must configure the import resolver for TypeScript. The exact syntax changes, but the concept does not.

Step by Step Execution

Consider this file structure:

src/
  app.ts
  app.spec.ts

And this code in app.spec.ts:

import app from './app';

What happens without the TypeScript resolver

  1. ESLint opens app.spec.ts.
  2. @typescript-eslint/parser successfully parses the TypeScript syntax.
  3. The rule import/no-unresolved checks ./app.
  4. The default import resolver looks for files using its normal resolution rules.
  5. It may not treat ./app.ts as a valid target.
  6. ESLint reports:
Unable to resolve path to module './app'

What happens after compiling

If app.js is generated:

  1. ESLint checks ./app again.
  2. The resolver now finds ./app.js.
  3. The warning disappears.

What happens with eslint-import-resolver-typescript

  1. ESLint opens app.spec.ts.
  2. The TypeScript parser reads the file.
  3. The TypeScript resolver checks ./app.
  4. It understands that ./app can map to ./app.ts.
  5. The module is resolved successfully.
  6. No false unresolved-import error appears.

Small trace example

// math.ts
export default function add(a: number, b: number) {
  return a + b;
}

// math.spec.ts
import add from './math';

console.log(add(2, 3));

With correct resolver settings:

  • ./math resolves to ./math.ts
  • ESLint is satisfied
  • TypeScript is satisfied
  • no generated .js file is needed for linting

Real World Use Cases

This configuration is useful in many real projects:

Backend APIs

A Node.js API written in TypeScript often has imports like:

import server from './server';
import routes from './routes';

You want linting to work before build output exists.

React and frontend apps

React projects often import components without extensions:

import Button from './Button';
import useAuth from './useAuth';

These may point to .tsx or .ts files.

Test files

Test files commonly import source modules directly:

import app from './app';

Linting should work even when tests run against source files rather than compiled files.

Path aliases

Many TypeScript projects use aliases such as:

import logger from '@/utils/logger';

A TypeScript-aware resolver is often necessary for those aliases too.

Monorepos

In a monorepo, package imports and local TypeScript modules may be resolved from multiple tsconfig files. A resolver can help ESLint understand that structure.

Take Quiz

Real Codebase Usage

In real codebases, developers usually combine TypeScript import resolution with a few common patterns.

1. Lint source code, not build output

Teams usually lint .ts and .tsx files directly and ignore generated dist/ or build/ folders.

{
  "ignorePatterns": ["dist", "build"]
}

2. Use extensionless imports consistently

Most TypeScript projects prefer:

import app from './app';

instead of:

import app from './app.ts';

This keeps imports cleaner and works better across build tools.

3. Align ESLint with tsconfig.json

If your TypeScript config defines base paths or aliases, the resolver should read the same tsconfig.json so both tools agree.

4. Validate dependencies early

Import rules are often used together with:

  • unused import detection
  • cycle detection
  • restricted internal imports
  • package boundary rules

5. Use guard clauses in config troubleshooting

When debugging unresolved imports, developers usually check these first:

  • Does the file actually exist?
  • Is the path relative to the current file?
  • Is the resolver installed?
  • Is eslint-plugin-import enabled?
  • Is the correct tsconfig.json being used?

6. Handle mixed environments

Some projects contain both JS and TS files. In that case, configuring both Node and TypeScript resolvers is common so tooling behaves predictably.

Common Mistakes

Here are common reasons this error continues even after adding TypeScript support.

Mistake 1: Only setting the parser

Broken idea:

{
  "parser": "@typescript-eslint/parser"
}

Why it fails:

  • This only helps ESLint parse TypeScript syntax.
  • It does not configure import resolution.

Fix:

  • Install and configure eslint-import-resolver-typescript.

Mistake 2: Forgetting eslint-plugin-import

Broken config:

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"]
}

Why it fails:

  • The rule import/no-unresolved belongs to eslint-plugin-import.

Fix:

{
  "plugins": ["@typescript-eslint", "import"]
}

Mistake 3: Installing the plugin but not the resolver

Broken assumption:

  • eslint-plugin-import alone will understand TypeScript imports.

Why it fails:

  • The plugin provides the rule.
  • The resolver teaches that rule how to locate .ts files.

Mistake 4: Wrong tsconfig.json path

Broken config:

{
  "settings": {
    "import/resolver": {
      "typescript": {
        "project": "./wrong-tsconfig.json"
      }
    }
  }
}

Why it fails:

  • ESLint reads the wrong project settings.

Fix:

  • Point to the correct tsconfig.json.

Mistake 5: Including file extensions incorrectly

Less ideal in many TypeScript projects:

import app from './app.ts';

Why to avoid it:

  • Many tools prefer extensionless imports.
  • It can create friction across bundlers, transpilers, and runtimes.

Prefer:

import app from './app';

Mistake 6: Confusing lint errors with TypeScript errors

These are different:

  • TypeScript compiler errors come from tsc
  • ESLint import resolution errors come from ESLint rules

A project can pass one and fail the other if configs are not aligned.

Comparisons

ConcernTool/SettingWhat it doesWhat it does not do
Parsing TypeScript syntax@typescript-eslint/parserLets ESLint read .ts and .tsx filesDoes not resolve imports
Import ruleseslint-plugin-importAdds rules like import/no-unresolvedDoes not automatically understand TS paths
TypeScript import resolutioneslint-import-resolver-typescriptResolves .ts, .tsx, and TS path mappingsDoes not replace the parser
Type checkingtypescript / tscChecks types and TypeScript semanticsDoes not run ESLint rules

import/no-unresolved vs TypeScript compiler

AspectESLint import/no-unresolvedTypeScript compiler
PurposeLint import validityCompile and type-check code
Understands TS automaticallyNoYes
Needs resolver configYesNo
Runs on source filesYesYes

Node resolver vs TypeScript resolver

ResolverBest forLimitation
Node resolverPlain JavaScript projectsMay miss .ts resolution details
TypeScript resolverTypeScript projects and aliasesNeeds proper tsconfig setup
Take Quiz

Cheat Sheet

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint", "import"],
  "parserOptions": {
    "project": "./tsconfig.json",
    "sourceType": "module"
  },
  "settings": {
    "import/resolver": {
      "typescript": {
        "project": "./tsconfig.json"
      }
    }
  },
  "rules": {
    "import/no-unresolved": "error"
  }
}

Install

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import eslint-import-resolver-typescript

Remember

  • Parser reads TypeScript syntax.
  • Plugin adds import rules.
  • Resolver finds .ts and .tsx files.
  • tsc and ESLint are separate tools.
  • import './app' should resolve to app.ts when the TypeScript resolver is configured.

Good default import style

import app from './app';

Common fix checklist

  • @typescript-eslint/parser installed
  • eslint-plugin-import installed
  • eslint-import-resolver-typescript installed
  • settings.import/resolver.typescript configured
  • correct tsconfig.json path
  • ESLint restarted after config changes

FAQ

Why does ESLint fail even though TypeScript can find the file?

Because TypeScript and ESLint use different systems. TypeScript understands .ts files, but ESLint's import rule needs a resolver configured separately.

Is @typescript-eslint/parser enough to fix import/no-unresolved?

No. It only parses TypeScript syntax. You also need a TypeScript-aware import resolver.

Which package usually fixes this problem?

eslint-import-resolver-typescript is the usual fix when using eslint-plugin-import with TypeScript.

Should I import ./app.ts explicitly?

Usually no. In most TypeScript projects, extensionless imports like ./app are preferred.

Do I still need eslint-plugin-import if I use TypeScript?

If you want rules like import/no-unresolved, yes. That rule comes from eslint-plugin-import.

Can this also fix path aliases from tsconfig.json?

Yes, in many cases. The TypeScript resolver can read path mappings from your tsconfig.json.

Why did the error disappear after compiling to JavaScript?

Because the default resolver could find the generated .js file, even though it could not correctly resolve the original .ts source file.

Should I disable import/no-unresolved instead of configuring a resolver?

Usually no. It is better to configure the resolver correctly so the rule remains useful.

Take Quiz

Mini Project

Description

Create a tiny TypeScript project with two files that import each other through normal extensionless imports, then configure ESLint so import/no-unresolved works without compiling to JavaScript first. This demonstrates the difference between parsing TypeScript and resolving TypeScript modules.

Goal

Set up ESLint so a TypeScript import like import app from './app'; passes linting before any .js files are generated.

Requirements

  1. Create a TypeScript file that exports a default value.
  2. Create another TypeScript file that imports it using an extensionless relative path.
  3. Enable the import/no-unresolved rule.
  4. Configure ESLint to resolve .ts files correctly.
  5. Verify the setup works without compiling the project first.
Take Quiz

Keep learning

Related questions

View more