Fix the baseUrl error that TypeScript 6 raises in older Angular workspaces

A tsconfig.json with the line \"baseUrl\": \"./\" struck out in red, and the path alias rewritten as \"@app/*\": [\"./src/app/*\"] in green. Above it, the TS5101 error ng build prints on Angular 22; below it, the build passing.
COMPANION REPO · LIVE FROM GITHUB
X [ERROR] TS5101: Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
  Visit https://aka.ms/ts6 for migration information. [plugin angular-compiler]

That's what ng build prints when you upgrade an older workspace to Angular 22, if it was created with any CLI from version 7 to 16. Angular 22 builds with TypeScript 6.0, and TypeScript 6.0 treats the baseUrl line in your tsconfig as an error. The fix is small: delete baseUrl and put ./ in front of every path alias. The suggested ignoreDeprecations line works too, but I'd only use it to get the upgrade merged, because TypeScript 7 drops baseUrl whatever you put next to it.

Why the upgrade didn't catch it

From version 7 to 16, the Angular CLI wrote "baseUrl": "./" into the tsconfig of every new workspace. Version 17 stopped. Path aliases in those workspaces usually sit on top of it, like "@app/*": ["src/app/*"]. Nobody has a reason to open that file once it works.

I expected ng update to deal with it, since the same update edits other tsconfig settings for you. So I made a fresh Angular 21 workspace, added baseUrl and an alias, and ran ng update to 22. It bumped TypeScript to 6.0.3, ran its migrations, and said nothing about baseUrl, not even a warning. The root tsconfig.json was untouched, and the next build failed with the error above. None of the Angular 22 migrations even mention it.

Removing baseUrl takes two edits

Deleting just that line swaps one error for another:

X [ERROR] TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'? [plugin angular-compiler]

Without baseUrl, TypeScript reads each path relative to the tsconfig file it's written in, and it wants that spelled out with ./. So the change is the deleted line plus a ./ on every alias. Here's the workspace's paths block after the fix, with both aliases resolving again:

"compilerOptions": {
  "paths": {
    "@app/*": ["./src/app/*"],
    "@env/*": ["./src/environments/*"]
  },
  ...
}
NG BUILD · TWO EDITS · NG BUILD
FIRST BUILD AFTER THE UPGRADE
$ ng build
✗ TS5101: Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0.
TSCONFIG.JSON
"baseUrl": "./",
"paths": {
"@app/*": ["./src/app/*"],
"@env/*": ["./src/environments/*"]
}
AFTER THE EDIT
$ ng build
✓ Application bundle generation complete.
delete baseUrl, then put ./ in front of every path: deleting the line alone fails with TS5090

There's a codemod for this, ts5to6, linked from the TypeScript 6.0 release notes. I ran npx @andrewbranch/ts5to6 --fixBaseUrl on a copy of the workspace and it produced exactly my hand edit, plus one line I'll get to below. For a workspace like this one, with one tsconfig and two aliases, I'd still do it by hand. It's three lines, and you want to read that diff anyway. I'd reach for the codemod in a workspace with a projects/ folder full of libraries, each with its own tsconfig.

Imports that only worked because of baseUrl

baseUrl did a second job that's easy to forget. It made every non-relative import look in the workspace root first. So an import like src/app/shared/format-name, with no ./ and no alias, compiled fine. My demo has one of those on purpose.

I got that import wrong at first, which taught me something. I wrote it as app/shared/format-name, and got a "Cannot find module" error on top of the one I was looking for. With the CLI's "baseUrl": "./", the lookup starts at the folder that holds tsconfig.json, not at src. So in these workspaces, the imports to hunt for start with src/.

After the fix, the src/ import broke with TS2307: Cannot find module 'src/app/shared/format-name'. The two aliases kept working. The codemod had noticed this one: its extra line was a catch-all entry, "*": ["./*"], which brings the old lookup back. It left that line out on a copy where I'd converted the import first.

The catch-all works, and the build passed with it. I'd still convert the imports to aliases and leave it out. It brings back the very behaviour TypeScript deprecated. I tried it in a scratch folder, and a file called rxjs.ts in the root won over the installed package of the same name. That's a strange bug to chase. If a codebase has hundreds of these imports, I'd keep the catch-all for one release and convert them in a follow-up.

How long ignoreDeprecations buys you

Setting "ignoreDeprecations": "6.0" next to baseUrl made the build pass straight away. That's fine when the Angular upgrade is already a big pull request and you don't want tsconfig changes mixed into it.

It only lasts while you're on TypeScript 6, though. I ran TypeScript 7.0.2 over the same workspace with both lines still in, and it refused:

tsconfig.app.json(5,3): error TS5090: Non-relative paths are not allowed. Did you forget a leading './'?
tsconfig.app.json(5,3): error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
  Use '"paths": {"*": ["./*"]}' instead.

The ignore line didn't help at all. TypeScript 7 doesn't even complain about it, it just stops mattering.

So I'd push back on an ignoreDeprecations line in code review unless it comes with a ticket to remove it. On the fixed workspace, TypeScript 7 type-checked without a complaint. Angular 22 itself still needs TypeScript 6.0, so the deadline isn't today. But the edit is the same now as it will be then.

Before you run ng update on an older workspace, look in the root tsconfig.json for baseUrl. ng update left that file alone when I tried it.