import("./models.js").Product inside JSDoc braces refers to the type Product exported from another module. Nothing is imported when the code runs; TypeScript resolves the path the same way it resolves a real import, so the extension and your moduleResolution setting apply. TypeScript 2.9 added import types.
Writing the path at every use gets long, so a common pattern is a local alias: /** @typedef {import("./models.js").Product} Product */ once at the top of the file. Since TypeScript 5.5, the @import tag does the same job with ES import syntax.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| A type from another module | /** @param {import("./models.js").Product} product */ | (product: import("./models.js").Product) |
| A local alias | /** @typedef {import("./models.js").Product} Product */ | import type { Product } from "./models.js"; |
| A whole module's exports | /** @param {typeof import("./config.js")} config */ | (config: typeof import("./config.js")) |
Examples
/**
* @param {import("./models.js").Product} product
* @param {number} quantity
* @returns {import("./models.js").LineItem}
*/
export function lineFor(product, quantity) {
return { sku: product.sku, quantity, total: product.price * quantity };
}
/** @param {typeof import("./config.js")} config the whole module's exports */
export function currency(config) {
return config.CURRENCY;
}export function lineFor(product: import("./models.js").Product, quantity: number): import("./models.js").LineItem {
return { sku: product.sku, quantity, total: product.price * quantity };
}
export function currency(config: typeof import("./config.js")) {
return config.CURRENCY;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @typedef {import("./models.js").Product} Product */
/** @param {Product[]} products */
export function cheapest(products) {
return products.reduce((a, b) => (b.price < a.price ? b : a));
}
/** @param {Product} product */
export function label(product) {
return `${product.name}: $${product.price.toFixed(2)}`;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @param {import("./model.js").Product} product */error TS2307: Cannot find module './model.js' or its corresponding type declarations.
export function priceOf(product) {
return product.price;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} typeof import()
typeof import("./config.js") is the type of the module's exports object, values included: config.CURRENCY is typed from export const CURRENCY = "USD". Use it to type a parameter that receives a module, such as a set of settings passed around for testing.
What can be imported
Any exported type: a @typedef or @callback from a .js module, an interface or type from a .ts or .d.ts file, a class (as its instance type), and types from packages, import("express").Request, when the package ships types or has an @types package installed.
Gotchas
- A wrong path is TS2307 on the JSDoc line, as the third example shows; the type is then unusable everywhere it appears.
- In ES modules with
module: nodenext, the path needs its.jsextension, as in animportstatement.
Questions
What is the difference between import() types and @import?
Both bring a type from another module into JSDoc without a run-time import. import("./x.js").Name works since TypeScript 2.9, right where the type is used; @import { Name } from "./x.js" needs TypeScript 5.5 and declares the name once per file.
Sources
- TypeScript 2.9: import types · TypeScript release notes
- JSDoc Reference: import types · TypeScript handbook
Last reviewed 20 October 2026.