JavaScript Named Exports: A Complete Tutorial

Named exports let you share multiple values from a JavaScript module by name. Learn the syntax, import patterns, aliasing, and re-exporting with clear examples.

6 min read

JavaScript named exports let you share multiple values from one module to another by name. Instead of exporting a single default value, you mark individual variables, functions, or classes with the export keyword, and other files import them by name using curly braces.

Here is the smallest useful example. One file exports two functions, and another file imports exactly the ones it needs:

javascriptjavascript
// math.js
export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}

The export keyword in front of each function declaration makes it available to other modules. The second file now imports only the functions it needs:

javascriptjavascript
// app.js
import { add, subtract } from "./math.js";
 
console.log(add(5, 3));      // 8
console.log(subtract(10, 4)); // 6

The import uses destructuring-style curly braces to pull in exactly the names you need. You write the names between the braces, and JavaScript matches them to the exported values from the source module.

Export Syntax Patterns

You can apply the export keyword in several ways. Each pattern below is valid, and the choice depends on how you prefer to organize your code.

Inline Export on Declaration

Put export directly before the declaration. This is the most common pattern because the visibility is immediate:

javascriptjavascript
// constants.js
export const API_URL = "https://api.example.com";
export const MAX_RETRIES = 3;
 
export function fetchUser(id) {
  return fetch(`${API_URL}/users/${id}`);
}

You know at a glance which values leave the module because the export keyword sits right next to each declaration. This inline style is the most readable pattern for small modules, keeping exports tightly coupled to their definitions.

The same approach works for classes too, which are commonly exported this way in object-oriented code. Here is how a class looks with the export keyword applied directly to its declaration statement:

javascriptjavascript
export class UserService {
  constructor(token) {
    this.token = token;
  }
}

Any file can now import and instantiate the class with its constructor argument.

Export List at the Bottom

Declare everything privately, then export only what you want to share at the end of the file. This gives you a clean public API summary in one place:

javascriptjavascript
// utils.js
function formatDate(date) {
  return date.toISOString().split("T")[0];
}
 
function formatCurrency(amount) {
  return `$${amount.toFixed(2)}`;
}

The internal helper function stays private by never appearing in the export statement at the bottom of the source file:

javascriptjavascript
function internalHelper() {
  return "helper";
}
 
export { formatDate, formatCurrency };

The function internalHelper stays private to the module because it is not listed in the export statement. No other file can access it.

Importing Named Exports

When you import named exports, the names must match exactly. The import statement is a static declaration that goes at the top of the file and cannot be wrapped in conditionals:

javascriptjavascript
import { add, subtract } from "./math.js";

If you try to import a name that does not exist, the browser throws an error. Here is what you would see:

texttext
SyntaxError: The requested module './math.js' does not provide an export named 'multiply'

Renaming Imports with the as Keyword

If two modules export the same name, or you want a clearer local name, use the as keyword to rename during import:

javascriptjavascript
import { formatDate as format } from "./date-utils.js";
import { formatDate } from "./logger.js";
 
// format refers to date-utils version, formatDate refers to logger version
console.log(format(new Date()));

This avoids naming collisions without changing either source module.

Import All Named Exports as a Namespace

When you need everything from a module, import it all under a single namespace object. This creates one convenient reference that holds every named export:

javascriptjavascript
import * as MathUtils from "./math.js";
 
console.log(MathUtils.add(2, 3));       // 5
console.log(MathUtils.subtract(10, 2)); // 8

The star import creates an object whose properties are every named export. This pattern is useful when a module has many exports and you want to group them under a clear prefix.

Renaming on Export

You can also rename during export with the as keyword. This is useful when the internal name of a function differs from the public API name you want to expose to consumers of your module.

It also helps when refactoring: change the internal name freely, and keep the exported name stable so existing importers do not break. Here is how it works:

javascriptjavascript
// store.js
function fetchUserData(id) {
  return { id, name: "Alice" };
}
 
export { fetchUserData as getUser };

Now consumers import it as getUser, not fetchUserData. The import side does not need to know the original internal name, and you can change fetchUserData later without touching every file that imports the module:

javascriptjavascript
import { getUser } from "./store.js";
console.log(getUser(1)); // { id: 1, name: "Alice" }

The import side does not need to know the original internal name.

Re-Exporting from Barrel Files

A barrel file collects exports from several modules and re-exports them from one place. This lets consumers import from a single path instead of hunting through multiple files.

Barrel file re-export pattern

Three utility modules each export their functions. The barrel file joins them together, so app.js only needs one import line. Here are the individual source files, each exporting its own functions for other modules to import:

javascriptjavascript
// auth.js
export function login() { /* ... */ }
export function logout() { /* ... */ }
 
// validation.js
export function isValidEmail(email) { /* ... */ }
 
// formatting.js
export function formatDate(date) { /* ... */ }

The barrel file re-exports all of them from one central location, collecting every exported function into a single convenient module:

javascriptjavascript
// index.js (barrel file)
export { login, logout } from "./auth.js";
export { isValidEmail } from "./validation.js";
export { formatDate } from "./formatting.js";

Now the consumer imports everything from one clean path, without needing to know which individual files contain each exported function:

javascriptjavascript
// app.js
import { login, logout, isValidEmail, formatDate } from "./utils/index.js";

The barrel file is the public face of the utils folder. Consumers do not need to know the internal file organization.

You can also re-export everything from another module at once with a wildcard:

javascriptjavascript
export * from "./auth.js";
export * from "./validation.js";

This pattern is shorter but hides which names are actually re-exported. Use the explicit form when the public API should be deliberate and easy to audit.

Common Mistake: Confusing Named and Default Import Syntax

Beginners often mix up the import syntax for named and default exports. The critical difference between them is the presence or absence of curly braces around the imported name.

With a default export, you import without braces. With a named export, braces are required. Getting this wrong gives you undefined instead of the function you expected:

javascriptjavascript
// math.js
export function add(a, b) { return a + b; }
 
// Wrong -- imports the default export, which does not exist here
import add from "./math.js";  // add is undefined
 
// Correct -- curly braces tell JavaScript to look for a named export
import { add } from "./math.js";  // add is the function

Curly braces around the import name are not optional decoration. They tell JavaScript to look for a specific named export rather than the default one. If you forget them, the import silently resolves to undefined rather than throwing an obvious error, which can be hard to debug.

Common Mistake: Trying to Reassign Imported Values

Imported bindings are read-only by design. You cannot reassign them directly, and attempting to do so throws a runtime error.

This is true for both named and default imports, because the module system enforces immutability on imported values. The restriction exists because modules are meant to be predictable: if one file could change another file's exports, debugging would become chaotic.

javascriptjavascript
import { API_URL } from "./config.js";
API_URL = "https://new-url.com"; // TypeError

If you need a local copy you can modify, assign it to a new variable first. The import binding itself stays untouched, and your local variable is free to change as needed. This pattern is common when you need to apply overrides, transformations, or fallback values without affecting the original imported constant:

javascriptjavascript
import { API_URL } from "./config.js";
 
let url = API_URL;
url = "https://override-url.com"; // This works fine

This local-copy pattern is reliable and keeps the module's contract intact. The original export remains unchanged for every other consumer.

When to Use Named Exports

Use named exports when a module has several related utilities, constants, or functions that consumers may want to pick and choose from. This is the most common pattern for utility modules, helper libraries, and shared constants. Named exports also work better with tree shaking because bundlers can statically analyze which exports are used and remove the rest -- a key advantage for production bundle sizes.

If a module has only one primary value to share, consider a default export instead. For modules that need both, pair a default export with a few named helper exports. This hybrid pattern is common in popular libraries and pairs well with the ES6 module import/export system that all modern JavaScript projects use.

Rune AI

Rune AI

Key Insights

  • Named exports let you export multiple values from a single module.
  • Import them with destructuring syntax using curly braces and the exact name.
  • Use the as keyword to rename exports or imports for clarity.
  • Re-export from barrel files to create clean public APIs.
  • Named exports work better with tree shaking than default exports.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between named and default exports?

Named exports let you export multiple values from a module, each with its own name. You import them using curly braces with the exact name. Default exports let you export one main value per module, and you can import it with any name you choose.

Can a module have both named and default exports?

Yes. A module can have one default export and any number of named exports. This is common in library code where the main function is the default export and helper utilities are named exports.

Do I need to use curly braces when importing named exports?

Yes. Named exports must be imported with curly braces using the exact exported name, unless you use an alias with the `as` keyword.

Conclusion

Named exports are the standard way to share multiple values from a JavaScript module. They keep imports explicit and let bundlers tree-shake unused code. Use them when a module exposes several related functions, constants, or classes. Pair them with default exports when a module has one main value and several helper utilities.