Skip to content
On this page

In TypeScript 5.0, when an import path ends in an extension that isn't a known JavaScript or TypeScript file extension, the compiler will look for a declaration file for that path in the form of {file basename}.d.{extension}.ts. For example, if you are using a CSS loader in a bundler project, you might want to write (or generate) declaration files for those stylesheets:

css
/* app.css */
.cookie-banner {
  display: none;
}
/* app.css */
.cookie-banner {
  display: none;
}
ts
// app.d.css.ts
declare const css: {
  cookieBanner: string;
};
export default css;
// app.d.css.ts
declare const css: {
  cookieBanner: string;
};
export default css;
ts
// App.tsx
import styles from "./app.css";

styles.cookieBanner; // string
// App.tsx
import styles from "./app.css";

styles.cookieBanner; // string

By default, this import will raise an error to let you know that TypeScript doesn't understand this file type and your runtime might not support importing it. But if you've configured your runtime or bundler to handle it, you can suppress the error with the new --allowArbitraryExtensions compiler option.

Note that historically, a similar effect has often been achievable by adding a declaration file named app.css.d.ts instead of app.d.css.ts - however, this just worked through Node's require resolution rules for CommonJS. Strictly speaking, the former is interpreted as a declaration file for a JavaScript file named app.css.js. Because relative files imports need to include extensions in Node's ESM support, TypeScript would error on our example in an ESM file under --moduleResolution node16 or nodenext.

For more information, read up the proposal for this feature and its corresponding pull request.