JavaScript Named Exports a Complete Tutorial

A complete tutorial on JavaScript named exports. Covers inline and bottom-of-file export syntax, import with renaming, namespace imports, re-exporting, tree-shaking benefits, barrel files, and why many teams prefer named exports over defaults for large codebases.

JavaScriptintermediate
11 min read

Named exports let you expose multiple bindings from a JavaScript module, each identified by a specific name. Importers must use that exact name (or explicitly alias it), which enforces consistency across a codebase and enables bundlers to tree-shake unused exports.

Inline Named Exports

Place export before a declaration to make it a named export:

javascriptjavascript
// math.js
export const PI = 3.14159;
export const E  = 2.71828;
 
export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}
 
export class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
 
  magnitude() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

Every export-prefixed declaration becomes part of the module's public API.

List Export (Bottom-of-File)

Declare everything first, then export selected bindings in one statement:

javascriptjavascript
// string-utils.js
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
 
function slugify(str) {
  return str.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
}
 
function truncate(str, maxLen = 100) {
  return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
}
 
// Internal helper — not exported
function normalizeWhitespace(str) {
  return str.replace(/\s+/g, " ").trim();
}
 
export { capitalize, slugify, truncate };

This style makes the public API visible in one place and keeps private helpers unexported.

Importing Named Exports

javascriptjavascript
import { add, subtract, PI } from "./math.js";
 
console.log(add(2, 3));  // 5
console.log(PI);          // 3.14159

Import With Alias

javascriptjavascript
import { add as sum, subtract as minus } from "./math.js";
 
console.log(sum(2, 3));   // 5
console.log(minus(5, 2)); // 3

Aliasing is useful to avoid name collisions when importing from multiple modules. For the destructuring equivalent of renaming, see renaming variables during JS destructuring guide.

Namespace Import

javascriptjavascript
import * as math from "./math.js";
 
console.log(math.PI);          // 3.14159
console.log(math.add(2, 3));   // 5
console.log(math.Vector);      // [class Vector]

The namespace object contains all named exports (and .default if a default exists).

Renaming on Export

javascriptjavascript
// internal.js
function fetchUserData(id) { /* ... */ }
function fetchOrderData(id) { /* ... */ }
 
export {
  fetchUserData as getUser,
  fetchOrderData as getOrder,
};

Consumers see getUser and getOrder. The internal names remain private to the module.

Re-Exporting Named Exports

Barrel files aggregate exports from multiple modules:

javascriptjavascript
// services/index.js
export { getUser, updateUser } from "./userService.js";
export { getOrder, createOrder } from "./orderService.js";
export { sendEmail } from "./emailService.js";

Re-Export Everything

javascriptjavascript
// utils/index.js
export * from "./string-utils.js";
export * from "./date-utils.js";
export * from "./number-utils.js";

If two modules export the same name, export * causes an ambiguity error. Use explicit re-exports to resolve conflicts.

Tree-Shaking and Named Exports

Bundlers (webpack, Rollup, Vite) perform tree-shaking by removing unused exports from the final bundle:

javascriptjavascript
// All exported individually
export function a() { /* ... */ }
export function b() { /* ... */ }
export function c() { /* ... */ }
javascriptjavascript
// Only 'a' is imported — b and c are tree-shaken out
import { a } from "./utils.js";
Export StyleTree-Shakable?Why
Named exportsYesEach export is independently trackable
Default export (function/class)PartiallyDepends on bundler analysis
Default export (object)NoBundler cannot determine which properties are used

This is the primary technical reason to prefer named exports in libraries.

Named vs Default: When to Choose

CriteriaNamed ExportDefault Export
Import syntaximport { X } fromimport X from
Multiple per moduleUnlimitedOne per module
Name consistencyEnforced (must match or alias)Consumer chooses any name
IDE autocompleteExcellentLimited
Refactoring supportStrongWeak
Tree-shakingFull supportLimited for objects
Best forUtility modules, librariesSingle-class modules, configs

See JavaScript default exports complete tutorial for the default export perspective.

Practical Patterns

Feature Module Barrel

javascriptjavascript
// features/auth/index.js
export { AuthProvider } from "./AuthProvider.js";
export { useAuth } from "./useAuth.js";
export { LoginForm } from "./LoginForm.js";
export { SignupForm } from "./SignupForm.js";
export { AUTH_ROUTES } from "./routes.js";
javascriptjavascript
// App.js — clean import path
import { AuthProvider, useAuth, LoginForm } from "./features/auth/index.js";

Constants Module

javascriptjavascript
// constants/http.js
export const STATUS_OK          = 200;
export const STATUS_CREATED     = 201;
export const STATUS_BAD_REQUEST = 400;
export const STATUS_NOT_FOUND   = 404;
export const STATUS_SERVER_ERROR = 500;
 
export const METHODS = Object.freeze({
  GET:    "GET",
  POST:   "POST",
  PUT:    "PUT",
  DELETE: "DELETE",
  PATCH:  "PATCH",
});

Export Type Alongside Runtime Value

javascriptjavascript
// In TypeScript, named exports work for both types and values
export interface User {
  id: string;
  name: string;
  email: string;
}
 
export function createUser(data: Partial<User>): User {
  return { id: crypto.randomUUID(), name: "", email: "", ...data };
}

Common Mistakes

Forgetting Curly Braces

javascriptjavascript
// Wrong — imports the default (which may not exist)
import add from "./math.js";
 
// Correct — imports the named export 'add'
import { add } from "./math.js";

Exporting and Immediately Destructuring

javascriptjavascript
// This does NOT create named exports
const utils = { capitalize, slugify };
export default utils;
 
// Consumer can't tree-shake individual functions:
import utils from "./utils.js";
utils.capitalize("hello"); // works, but not tree-shakable
 
// Better: export individually
export { capitalize, slugify };
Rune AI

Rune AI

Key Insights

  • Each named export is independently importable: Consumers pick exactly what they need, nothing more
  • Tree-shaking works best with named exports: Unused named exports are removed from the bundle; default-exported objects are not shakable
  • Curly braces distinguish named from default imports: { X } is named, X is default -- mixing them up is a top source of import bugs
  • Barrel files centralize module APIs: Re-export from an index file to keep import paths clean without losing tree-shaking
  • Named exports produce live bindings: Changes to exported let variables are reflected in all importing modules in real time
RunePowered by Rune AI

Frequently Asked Questions

Can I mix named and default exports in one module?

Yes. A module can have one default and any number of named exports. Import them together: `import Default, { named1, named2 } from "./mod.js"`.

Do named exports work with dynamic import()?

Yes. `const { add, multiply } = await import("./math.js")`. Named exports are properties on the resolved module object. See [dynamic imports in JavaScript complete guide](/tutorials/programming-languages/javascript/dynamic-imports-in-javascript-complete-guide).

Can I export the same binding under multiple names?

Yes: `export { fn as name1, fn as name2 }`. Both `name1` and `name2` point to the same binding.

Are named export bindings live?

Yes. If the exporting module changes a `let` variable, importers see the updated value. This is different from CommonJS where values are snapshot-copied.

Does the order of exports matter?

No. Named exports form an unordered set. Import order also does not matter.

Conclusion

Named exports are the foundation of modular JavaScript. They enforce naming consistency, enable full tree-shaking, and work seamlessly with IDE tooling. For utility modules, constant files, and any module exporting multiple values, named exports are the standard recommendation. For the overall module system and re-export patterns, see JavaScript ES6 modules import export guide. For spread-based data manipulation within modules, see JS spread vs rest operator complete tutorial.