export type

Re-exports or exports a binding for type use only, so the declaration never appears in emitted JavaScript.

Signature
export type { T };
export type { T, U as V } from "mod";
export type * from "mod";
export type * as ns from "mod";
export { value, type T } from "mod";
  • Added in TypeScript 3.8

Parameters

ParameterTypeDescription
$TidentifierThe local or re-exported declaration to expose for type positions only.
$U as Voptionalrename clauseRenames the export, exactly as in a value export.
$"mod"optionalstring literalOptional module specifier. With it the form is a re-export; without it, a local declaration is exported type-only.
$type (inline modifier)optionalkeywordMarks a single specifier inside an ordinary export clause as type-only, letting one statement carry both values and types.

Errors

Re-exporting a type without the modifier while `isolatedModules` is on raises "Re-exporting a type when 'isolatedModules' is enabled requires using 'export type'."

Changelog

  • PHP 3.8`export type { ... }` introduced alongside `import type`.
  • PHP 4.5Inline `type` modifiers on individual export specifiers added.
  • PHP 5.0`export type *` and `export type * as ns` added, so a whole namespace can be re-exported type-only.
  • PHP 5.0`verbatimModuleSyntax` makes the modifier load-bearing: an export without `type` is emitted verbatim, even if every name in it is a type.

export type is the mirror image of import type: the binding it creates is usable only where a type is expected, and the statement is erased from the emitted JavaScript.

When to reach for it

Almost always in a barrel file — an index.ts that re-exports a package's public surface. This is the one place where the compiler cannot help you without the keyword. In export { Config } from "./types.js", a single-file transpiler has no idea whether Config is an interface or a const, so it emits the re-export; at runtime the property does not exist and consumers get undefined or, under strict ESM, a link-time failure. isolatedModules turns this into a compile error precisely to force the modifier.

It is also the way to publish a type under a different name than it has internally, or to expose a type while keeping the implementation module private.

What trips people up

export type * from "./mod" re-exports only the types of a module, silently dropping its values — which is what you want for a types-only barrel and a nasty surprise if you meant to re-export everything. And export type { Foo } where Foo is a class exports the type but not the constructor, so consumers can annotate with it but not instantiate it.

Example

Example · typescript
// types.ts
export interface Config { port: number; host: string }
export const DEFAULT_PORT = 8080;

// index.ts  (barrel)
export type { Config } from "./types.js"; // erased entirely
export { DEFAULT_PORT } from "./types.js"; // real re-export

// app.ts
import type { Config } from "./index.js";
import { DEFAULT_PORT } from "./index.js";

const cfg: Config = { port: DEFAULT_PORT, host: "localhost" };
console.log(cfg);
// -> { port: 8080, host: "localhost" }

When to use it

  • Re-export interfaces from a package's index.ts without emitting a dead runtime re-export
  • Publish a type under a public name while the implementation module stays internal
  • Satisfy the isolatedModules re-export error when migrating a barrel file to esbuild or swc
  • Expose a library's option types while keeping its factory functions unexported

Frequently asked questions

Why does TypeScript say re-exporting a type requires export type?

Because `isolatedModules` is enabled and the compiler cannot prove at single-file granularity that the name is a type. Change `export { Foo } from "./foo.js"` to `export type { Foo } from "./foo.js"`, or add the inline modifier: `export { bar, type Foo } from "./foo.js"`.

Does export type work for classes and enums?

Yes, but consumers get only the type side. An `export type { MyClass }` lets others write `let x: MyClass`, while `new MyClass()` fails because the constructor value was never exported. Export a class normally if callers need to instantiate it.

What is the difference between export type * and export *?

`export type *` re-exports only the type declarations of the target module and emits nothing; `export *` re-exports everything, values included, and produces a real runtime re-export. Using the type-only form on a mixed module silently hides its functions, classes and constants.

More examples

Renaming on the way out, and the inline modifier

The emitted index.js contains only `export { HttpClient } from "./internal/http-client.js"`. The inline `type` modifier lets the class and its renamed options interface travel in one statement, which keeps the barrel readable and still satisfies verbatimModuleSyntax.

Example · typescript
// internal/http-client.ts
export interface HttpClientRequestOptions {
  method: "GET" | "POST";
  timeoutMs: number;
}
export class HttpClient {
  constructor(private opts: HttpClientRequestOptions) {}
  describe(): string { return `${this.opts.method} @${this.opts.timeoutMs}ms`; }
}

// index.ts — one statement carrying a value and a renamed type
export { HttpClient, type HttpClientRequestOptions as RequestOptions } from "./internal/http-client.js";

// app.ts
import { HttpClient, type RequestOptions } from "./index.js";

const opts: RequestOptions = { method: "GET", timeoutMs: 250 };
console.log(new HttpClient(opts).describe());
// -> "GET @250ms"

export type * versus export *

`types-only.js` compiles to an empty module: `export type *` erased the whole statement. `Point` is still reachable through it for annotations, but `ORIGIN` is not exported at all, which is the error the commented import would produce. Reach for `export type *` only when you genuinely want a types-only entry point.

Example · typescript
// shapes.ts
export interface Point { x: number; y: number }
export const ORIGIN: Point = { x: 0, y: 0 };

// types-only.ts
export type * from "./shapes.js";   // Point survives, ORIGIN does not

// everything.ts
export * from "./shapes.js";        // both survive

// app.ts
import type { Point } from "./types-only.js";
import { ORIGIN } from "./everything.js";

// tsc: Module './types-only.js' has no exported member 'ORIGIN'.
// import { ORIGIN as missing } from "./types-only.js";

const p: Point = { x: ORIGIN.x + 3, y: ORIGIN.y + 4 };
console.log(p);
// -> { x: 3, y: 4 }

Discussion

  • Be the first to comment on this lesson.