Back to News & Insights
Web Development September 29, 2026 · 13 min read

TypeScript 6.0 `--allowImportingTsExtensions`: What It Unlocks for Monorepo Setups in 2026

TypeScript 6.0 --allowImportingTsExtensions: What It Unlocks for Monorepo Setups in...

TypeScript 6.0 `--allowImportingTsExtensions`: What It Unlocks for Monorepo Setups in 2026

TypeScript 6.0 --allowImportingTsExtensions: What It Unlocks for Monorepo Setups in 2026

This article was written with the assistance of AI, under human supervision and review.

Most monorepo import failures stem from a single mismatch: TypeScript forbids .ts extensions in source code, but runtime module resolution often requires them. Teams build elaborate path-mapping workarounds, introduce build-time rewrite steps, or abandon explicit extensions entirely. The result is a fragile setup where a single misconfigured tsconfig.json silently breaks cross-package imports and surfaces only at bundle time.

TypeScript 6.0's --allowImportingTsExtensions eliminates that friction. When enabled, it permits import { fn } from "./utils.ts" in source files without triggering the TS1479 error. The compiler stops enforcing the legacy rule that import specifiers must omit file extensions for non-declaration files. This matters because modern bundlers like Vite and esbuild resolve .ts paths natively, and monorepo package boundaries often demand explicit extensions to avoid Node.js resolution ambiguity.

The flag does not rewrite imports or emit JavaScript. It strictly removes the compile-time prohibition. Downstream tools (bundlers, loaders, Node.js with custom resolution hooks) handle the actual path resolution. The distinction is critical: --allowImportingTsExtensions unlocks authoring convenience; the runtime environment determines whether those imports execute correctly.

This distinction unlocks two immediate benefits for monorepos. First, it eliminates the need for path-rewrite plugins in TypeScript-native build tools. Second, it surfaces resolution failures earlier in the development loop because the compiler no longer masks incorrect paths behind a blanket extension rule. The failure mode shifts from "silent bundler error at production build time" to "immediate feedback during type-checking."

Key Takeaways --allowImportingTsExtensions permits .ts extensions in import specifiers without triggering the TS1479 error, but does not rewrite or emit those imports. Modern bundlers (Vite, esbuild, Turbopack) resolve .ts paths natively, making the flag viable in production monorepo setups as of 2026. The flag solves cross-package import failures in monorepos where workspace protocol or explicit extensions are required for correct resolution. --rewriteRelativeImportExtensions rewrites .ts to .js during emit and requires --noEmit or bundler-only workflows; --allowImportingTsExtensions does not modify output. Enabling the flag breaks Node.js native execution without custom loaders; reserve it for bundler-driven workflows or use tsx/ts-node with ESM hooks.

Monorepo setups introduce a coordination problem between TypeScript's module resolution and the package manager's workspace protocol. A typical workspace import looks like import { api } from "@scope/api/client". TypeScript resolves that path through tsconfig.json paths mappings. The package manager (npm, pnpm, Yarn) resolves it through workspace protocol links in nodemodules.

The failure mode appears when a shared package exports TypeScript source files directly instead of emitting JavaScript to a dist folder. Consider a structure where packages/shared contains only .ts files and exports them via package.json exports field. A consuming package writes import { util } from "@scope/shared/util". TypeScript type-checks successfully because paths maps @scope/shared/ to ../shared/. The bundler, however, follows exports to shared/util.ts. Without --allowImportingTsExtensions, the import specifier must omit .ts, creating an ambiguity: does util resolve to util.ts, util/index.ts, or util.js?

The traditional solution splits into two camps. Teams either add a build step to packages/shared that emits JavaScript, then point exports to the dist folder, or they configure the bundler with a custom resolver plugin that maps extensionless imports to .ts files. Both approaches introduce latency: the first requires pre-building every shared package before dependent packages can bundle, and the second couples the build configuration to a specific bundler's plugin API.

--allowImportingTsExtensions collapses that complexity. When enabled, the consuming package writes import { util } from "@scope/shared/util.ts". TypeScript permits the explicit extension. The bundler receives an unambiguous path. No intermediate build step runs, and no resolver plugin is required. The tradeoff is that the flag locks the workflow into bundler-based execution because Node.js native module resolution does not handle .ts extensions without a custom loader.

The flag activates in compilerOptions with one dependency: moduleResolution must be set to bundler or nodenext. The compiler enforces this constraint because the flag targets workflows where a bundler or modern loader handles TypeScript files directly. Attempting to enable it with moduleResolution: "node" triggers configuration error TS5110.

The noEmit: true setting frequently pairs with --allowImportingTsExtensions because the flag does not rewrite import specifiers during emit. If TypeScript emits JavaScript and the output contains import "./utils.ts", Node.js execution fails unless a runtime loader translates .ts to .js. Teams using the flag in monorepos typically delegate output generation entirely to bundlers (Vite, esbuild, Turbopack), making noEmit a natural fit.

Two edge cases require attention. First, if the project uses "module": "esnext" or "module": "nodenext", the compiler enforces that all import specifiers must be runtime-valid. The flag permits .ts extensions at type-check time, but the bundler must handle them at execution time or the build fails silently. Second, declaration emit (.d.ts generation) strips .ts extensions from imports. A source file containing import { T } from "./types.ts" emits import { T } from "./types" in the declaration file. This behavior prevents declaration consumers from inheriting the bundler dependency.

--allowImportingTsExtensions vs --rewriteRelativeImportExtensions: When to Use Each

TypeScript 5.7 introduced --rewriteRelativeImportExtensions as a sibling flag that serves a different use case. Where --allowImportingTsExtensions permits .ts imports without modification, --rewriteRelativeImportExtensions rewrites .ts to .js during emit. The distinction determines which workflows each flag supports.

--allowImportingTsExtensions requires noEmit: true or a bundler-driven workflow where TypeScript never writes JavaScript to disk. The source files contain .ts imports, the bundler receives those imports unchanged, and the bundler's native TypeScript resolver handles them. This pattern fits Vite, esbuild, and Turbopack setups where tsc runs only for type-checking (tsc --noEmit) and the bundler performs actual compilation.

--rewriteRelativeImportExtensions supports hybrid workflows where TypeScript emits JavaScript that Node.js executes directly. The source files write .ts imports for author convenience, but the emitted JavaScript contains .js extensions. This pattern fits Node.js ESM projects that require explicit extensions per the ECMAScript specification but want to avoid writing .js in TypeScript source code. The flag does not support absolute imports or package specifiers, only relative paths like ./util.ts or ../shared/api.ts.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation