Named Exports in TypeScript

Named exports let you share multiple values and types from a single TypeScript module. Learn every named export pattern and how TypeScript enforces correct usage across files.

6 min read

TypeScript named exports share a specific value, function, class, or type from a module using its declared name. The importing file must use the exact same name to bring it in. TypeScript checks every name at compile time, so a typo in an import is caught before the code runs.

Here is the simplest named export. The first file exports a logging function, and the second file imports and calls it:

typescripttypescript
// logger.ts
export function log(message: string): void {
  console.log(`[INFO] ${message}`);
}

The consumer imports exactly the name it needs and calls the function with a string argument. Notice the file uses ./logger.js, not ./logger.ts, because TypeScript resolves the extension back to the source file at compile time:

typescripttypescript
// app.ts
import { log } from "./logger.js";
 
log("Server started");

Running this prints [INFO] Server started to the console. The name log is bound at both ends: the module exports it, and the consumer imports it with the same name. TypeScript verifies that log exists in logger.ts, that it accepts a string, and that it returns void.

Inline Named Exports

The most direct pattern: put export right before the declaration. This works on functions, classes, variables, interfaces, and type aliases. Here is a module where every declaration is individually exported:

typescripttypescript
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}
 
export const PI = 3.14159;
 
export class Calculator {
  multiply(a: number, b: number): number {
    return a * b;
  }
}

Types can also be exported inline using the same pattern with export before the declaration. This works for interfaces, type aliases, and any other type-level declaration, and the syntax looks identical to exporting a value:

typescripttypescript
export interface Point {
  x: number;
  y: number;
}
 
export type ID = string | number;

Every declaration is individually marked for export. When you read the file, you immediately know what is public. This style works well for modules where most declarations are meant to be consumed externally.

TypeScript distinguishes between value exports and type exports automatically. The functions, the constant, and the class produce runtime values, while the interface and the type alias are type-only and erased during compilation.

Both can be imported with the same syntax from the consumer side.

Export Lists

Instead of marking each declaration individually, collect all exports in a single export statement at the end of the file. This gives a clear overview of the module's public API in one place.

typescripttypescript
// math.ts
function add(a: number, b: number): number {
  return a + b;
}
 
function subtract(a: number, b: number): number {
  return a - b;
}
 
const PI = 3.14159;
 
export { add, subtract, PI };

This pattern is especially useful when the module has many internal helpers that should not be exported alongside the few public functions. The export list at the bottom acts as a gate: only the names listed there are visible to consumers.

Rename Exports

Use the as keyword to export a value or type under a different name than its local declaration. The importing file sees the renamed version.

typescripttypescript
// formatters.ts
function formatPrice(amount: number, currency: string): string {
  return `${currency}${amount.toFixed(2)}`;
}
 
export { formatPrice as toPrice };

The rename only affects the export binding. The function is still called formatPrice inside formatters.ts, but any file that imports from this module only ever sees the name toPrice:

typescripttypescript
// checkout.ts
import { toPrice } from "./formatters.js";
 
console.log(toPrice(19.9, "$"));

The output is $19.90. This is useful when the local name makes sense inside the module but a more descriptive name is better for consumers.

Exporting Types

TypeScript types, interfaces, and enums can be exported just like values. After introducing the type, you can also mark re-exports as type-only to make the intent explicit.

typescripttypescript
// models.ts
export interface User {
  id: string;
  name: string;
  email: string;
}
 
export type UserRole = "admin" | "editor" | "viewer";
 
export enum Status {
  Active = "active",
  Inactive = "inactive",
}

Interfaces and type aliases are erased at compile time and produce no JavaScript. Enums produce a runtime object in the compiled output. TypeScript tracks this difference so you do not have to think about it when importing, but it matters when you use export type in re-export lists to prevent accidental runtime references.

Re-Exporting from Another Module

A module can import something and immediately export it again, creating a pass-through. The foundation of barrel files. Here are two modules that export functions, followed by a barrel that re-exports them:

typescripttypescript
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}
 
export function subtract(a: number, b: number): number {
  return a - b;
}

A second file re-exports both functions without ever importing them into a local variable first. This pass-through pattern is what makes barrel files possible:

typescripttypescript
// index.ts
export { add, subtract } from "./math.js";

The index.ts file acts as a proxy. Consumers import from index.js and get everything math.ts exports. For more on organizing re-exports, see barrel files in TypeScript.

Type-Only Re-Exports

When re-exporting only types, use export type to make the intent clear and ensure the re-export is erased from the compiled JavaScript.

typescripttypescript
// types.ts
export interface User {
  id: string;
  name: string;
}
 
export type Status = "active" | "inactive";

A barrel file re-exports both with export type instead of a plain export, marking the whole statement as type-only so it is guaranteed to disappear from the compiled output:

typescripttypescript
// index.ts
export type { User, Status } from "./types.js";

The export type statement guarantees that nothing from this re-export ends up in the JavaScript output. It is a pure compile-time operation. If you accidentally include a value in an export type list, TypeScript reports an error immediately.

Named Exports vs Default Exports

Named exports have several advantages over default exports for most TypeScript projects. The tradeoffs are summarized below.

Named exportsDefault exports
Import name is checked against the sourceImport name is arbitrary
Autocomplete lists every available exportAutocomplete cannot guess the name
Rename refactoring works across the projectRename refactoring only affects the local file
Multiple exports per moduleOne default export per module
Tree-shaking works predictablyTree-shaking depends on the bundler

Named exports make your module's public API explicit and tooling-friendly. Every import site uses the same name, so find-all-references and rename work reliably. For a deeper comparison, see default exports in TypeScript.

Common Mistakes

Exporting something that is never used outside the module. TypeScript warns about unused locals inside a module, but it will not warn about unused exports unless you use a linter. Keep your export list trim and only export what consumers genuinely need.

Renaming too aggressively during export. While renaming during export is valid, too many renames make the code hard to follow. If you need a different name for consumers, consider whether the internal name should simply be changed instead of adding a rename layer.

Forgetting to re-export types in barrel files. When building an index file that re-exports from multiple modules, remember to include type exports. A common mistake is re-exporting only values and leaving interfaces and type aliases inaccessible from the barrel. Always pair value re-exports with their corresponding type re-exports.

Rune AI

Rune AI

Key Insights

  • Named exports share specific values or types by their declared name.
  • Use inline export for declarations, export lists for grouping, and export type for type-only re-exports.
  • TypeScript checks every imported name against the source module at compile time.
  • Rename exports with as when the original name would be confusing in the importing file.
  • Re-exports let you aggregate exports from multiple modules through a single entry point.
RunePowered by Rune AI

Frequently Asked Questions

Can I export both types and values with named exports?

Yes. TypeScript treats type exports and value exports the same way in named export lists. You can write export { User, createUser } where User is an interface and createUser is a function. TypeScript tracks which is which at compile time.

What happens if I import a named export that does not exist?

TypeScript reports a compiler error: 'Module has no exported member'. This is one of the main advantages of named exports over default exports, since the name is checked immediately.

Should I use inline exports or an export list at the bottom?

Both are correct. Inline exports keep the export marker next to the declaration, which is easier to scan. Export lists at the bottom give a quick overview of everything a module exposes. Choose one style per project and stay consistent.

Conclusion

Named exports are the most predictable and tooling-friendly way to share code across TypeScript files. Every exported name is checked at compile time, autocomplete knows exactly what is available, and refactoring tools can rename exports safely across your entire project.