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.
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:
// 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:
// 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:
// 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:
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.
// 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.
// 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:
// 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.
// 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:
// 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:
// 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.
// 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:
// 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 exports | Default exports |
|---|---|
| Import name is checked against the source | Import name is arbitrary |
| Autocomplete lists every available export | Autocomplete cannot guess the name |
| Rename refactoring works across the project | Rename refactoring only affects the local file |
| Multiple exports per module | One default export per module |
| Tree-shaking works predictably | Tree-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
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
aswhen the original name would be confusing in the importing file. - Re-exports let you aggregate exports from multiple modules through a single entry point.
Frequently Asked Questions
Can I export both types and values with named exports?
What happens if I import a named export that does not exist?
Should I use inline exports or an export list at the bottom?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.