baseUrl

Sets the directory that bare import specifiers resolve against.

Signature
"baseUrl": string  // a directory path, relative to the tsconfig.json
  • Added in TypeScript 2.0
  • Deprecated since PHP 6.0

Parameters

ParameterTypeDescription
$baseUrlstringA directory path resolved relative to the tsconfig.json that declares it. "./" means the config's own folder; "./src" makes src the root for bare specifiers. Lookups here take priority over node_modules.

Return value

Adds a directory that non-relative (bare) import specifiers are resolved against, and supplies the root for relative paths entries.

Errors

Nothing directly. Under TypeScript 6.0 it reports a deprecation error unless "ignoreDeprecations": "6.0" is set. A file whose name collides with an installed package silently shadows that package, which surfaces as confusing type errors rather than a resolution failure.

Changelog

  • PHP 4.1paths no longer requires baseUrl; without it, paths entries resolve relative to the tsconfig.json file instead.
  • PHP 6.0Deprecated. baseUrl is no longer treated as a lookup root for module resolution — move the prefix it provided into the paths entries, e.g. "@app/*": ["./src/app/*"].

baseUrl names a directory that bare import specifiers are resolved against. With "baseUrl": "./", import { helloWorld } from "hello/world" looks for ./hello/world.ts next to the tsconfig.json — no ../../ required.

Why it is on the way out

Two problems. First, this lookup has higher priority than node_modules, so a folder named src/react will shadow the installed react package for every import in the project, and the resulting type errors point nowhere near the cause. Second, it is invisible: a bare specifier gives no hint whether it means a package or a local folder, which every bundler and reader has to guess at.

TypeScript 4.1 removed its last real job by letting paths work without it, and TypeScript 6.0 deprecated it outright.

What to do instead

Use paths with an explicit, unmistakable prefix. Replacing "baseUrl": "./src" with "paths": { "@app/*": ["./src/*"] } keeps the short imports, removes the shadowing hazard, and makes the alias obvious at every call site. The migration is mechanical: drop baseUrl and fold its prefix into each paths entry.

Example

Example · typescript
// tsconfig.json
// { "compilerOptions": { "baseUrl": "./" } }
//
// project
// ├── ex.ts
// ├── hello
// │   └── world.ts        export const helloWorld = "Hello, world";
// └── tsconfig.json

// ex.ts — a bare specifier, resolved from the baseUrl directory:
import { helloWorld } from "hello/world";

console.log(helloWorld);
// -> Hello, world

When to use it

  • Reading a legacy project that still resolves bare specifiers from src.
  • Migrating baseUrl-style absolute imports over to explicit paths prefixes.
  • Supporting an old AMD loader setup, the use case it was designed for.
  • Diagnosing why a local folder is shadowing an installed npm package.

Frequently asked questions

Is baseUrl still needed for paths?

No, since TypeScript 4.1 paths entries resolve relative to the tsconfig.json file when baseUrl is not set. TypeScript 6.0 deprecated baseUrl, so fold its prefix into the paths entries instead.

What replaces baseUrl for absolute imports?

An explicit paths mapping such as "@/*": ["./src/*"], which gives the same short imports without a bare specifier that could collide with a package. Remember the bundler or test runner needs the matching alias too.

Why does my local folder override an npm package?

Because baseUrl lookups take priority over node_modules, so src/react resolves before the installed react package. Remove baseUrl and use a prefixed paths alias, or rename the folder.

More examples

Migrating off baseUrl

paths entries resolve against the tsconfig.json when baseUrl is absent, so prepending ./src to each location reproduces the old behaviour exactly. Bare specifiers that relied on baseUrl alone, with no paths entry, need an explicit prefix added.

Example · typescript
// Before — TypeScript 6.0 reports baseUrl as deprecated:
// { "compilerOptions": {
//     "baseUrl": "./src",
//     "paths": { "@app/*": ["app/*"], "@lib/*": ["lib/*"] }
// } }

// After — the src prefix moves into each entry, baseUrl disappears:
// { "compilerOptions": {
//     "paths": { "@app/*": ["./src/app/*"], "@lib/*": ["./src/lib/*"] }
// } }

import { formatCents } from "@lib/money";   // -> ./src/lib/money.ts

console.log(formatCents(1999));
// -> $19.99

// Import sites do not change; only the config does.

The shadowing hazard

Because baseUrl is consulted before node_modules, any local directory can silently replace a dependency of the same name, project-wide. The reported error blames the package, not the folder, which makes this one of the harder resolution bugs to spot.

Example · typescript
// { "compilerOptions": { "baseUrl": "./src" } }
//
// src/
// ├── react/index.ts      <- a local helper folder, unluckily named
// └── app.ts

import { useState } from "react";
// baseUrl wins over node_modules, so this resolves to src/react/index.ts
// -> error TS2305: Module '"react"' has no exported member 'useState'.

// With paths instead, the intent is explicit and nothing is shadowed:
//   "paths": { "@/react/*": ["./src/react/*"] }
console.log(typeof useState);
// -> function   (only once the real react package resolves)

Discussion

  • Be the first to comment on this lesson.