Recipe · Modules and packages

How do I import and re-export types between JavaScript files?

  • Node.js (ES modules)
  • @import
  • @typedef
  • import() types
  • TypeScript 5.5
  • Checked with TypeScript 5.9

The problem I define Order and LineItem with @typedef in one file. How do I use them in other files, and how do I collect the types of several modules in one place for the rest of the app to import?

The short answer

Define the types with @typedef in the module they belong to. Re-export them from a types.js module with @typedef {import(…)}, and bring them into any file with @import:

checkout.js checkout.ts TypeScript 5.9.3 · checked 21 October 2026
import { orderTotal } from "./orders.js";

/** @import { Order, LineItem } from "./types.js" */

/** @type {LineItem} */
const lamp = { sku: "LAMP-01", cents: 3900 };

/** @type {Order} */
const order = { id: "A-1001", lines: [lamp] };

console.log(orderTotal(order));
import { orderTotal } from "./orders.js";

import type { Order, LineItem } from "./types.js";

const lamp: LineItem = { sku: "LAMP-01", cents: 3900 };

const order: Order = { id: "A-1001", lines: [lamp] };

console.log(orderTotal(order));
Types shared through @import and through a re-exporting module.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

checkout.js imports a real function from orders.js and only types from types.js; the types are checked exactly as if they were declared locally.

Why it works

Every top-level @typedef in a module is exported, so orders.js exports Order and LineItem without any extra syntax. @typedef {import("./orders.js").Order} Order in types.js declares a new type with the same name and shape, which types.js exports in turn: that is a re-export. export {} keeps types.js a module even though it has no values.

@import { Order, LineItem } from "./types.js" is TypeScript 5.5’s JSDoc form of import type. It reads like an import statement, works with several names at once, and imports nothing at runtime.

The same thing in TypeScript

The TypeScript equivalent tab writes import type { Order, LineItem } from "./types.js" and annotates the variables inline. A TypeScript types.ts would re-export with export type { Order, LineItem } from "./orders.js"; in JavaScript the @typedef line does that job.

What goes wrong

A @typedef is not a value, so importing it with a real import statement is an error, and tsc’s message (TS18042) tells you the JSDoc form to use instead. A name the module does not export fails in the @import comment (TS2305):

checkout.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
import { Order } from "./orders.js";error TS18042: 'Order' is a type and cannot be imported in JavaScript files. Use 'import("./orders.js").Order' in a JSDoc type annotation.

/** @import { Invoice } from "./orders.js" */error TS2305: Module '"./orders.js"' has no exported member 'Invoice'.

/** @type {Order} */
const order = { id: "A-1001", total: 0 };

/** @type {Invoice} */
const invoice = { number: 1 };
console.log(order, invoice);
A type imported like a value, and a type the module does not have.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Gotchas

  • Paths are module paths. @import and import() types use the same specifiers as real imports: under nodenext, relative paths need their .js extension.
  • A script file shares its typedefs globally. In a file without import or export, top-level @typedefs are global to the project, which can clash between files; export {} makes the file a module.
  • Unused @import names are not checked. A misspelled name that no type uses is not reported in TypeScript 5.9; it fails as soon as a JSDoc type refers to it.

Questions

Which should I use, @import or @typedef {import(…)}?

@import on TypeScript 5.5 and later: one line brings in several types, like an import type statement. @typedef {import("./orders.js").Order} Order also works on older versions (import types arrived in TypeScript 2.9) and is still the way to re-export a type under a name.

Does @import load the module at runtime?

No. It is a comment; only the checker and the editor read it. The module is loaded only if the file also imports it with a real import statement.

Sources

Last reviewed 21 October 2026.