JavaScript Default Exports: Complete Tutorial

Default exports let a module share one main value without requiring the importer to know its name. Learn the syntax, patterns, and when to use default vs named exports.

6 min read

A JavaScript default export is the single main value that a module shares with the world. Unlike named exports where you pick specific values by name, a default export says "this is the one thing this module is about." You import it without curly braces, and you can give it any local name you want.

Here is the simplest possible example. The source module exports one function as its default:

javascriptjavascript
// greet.js
export default function greet(name) {
  return `Hello, ${name}!`;
}

The consumer imports it without curly braces, using any name they choose. This is the key visual difference from named exports:

javascriptjavascript
// app.js
import greet from "./greet.js";
 
console.log(greet("Alice")); // Hello, Alice!

Notice there are no curly braces around the import name. Curly braces mean named exports, and no curly braces means the default export.

This distinction is the first thing to memorize about default exports.

Default Export Syntax Patterns

There are two ways to write a default export: inline with the declaration, or at the bottom of the file. Both produce the same result, and the choice is a matter of code organization preference.

Inline Default Export

Place export default directly before a function, class, or expression. This is the most common pattern for React components and single-purpose classes because the export and the declaration sit together:

javascriptjavascript
// UserService.js
export default class UserService {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }
 
  async fetchProfile(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
}

The export default prefix tells any importing file that this class is the module's primary value. You can see at a glance what the module provides.

Separate Declaration and Default Export

Declare the value first with a clear name, then export it at the bottom. This separates the definition logic from the export decision and is useful when the function body is long:

javascriptjavascript
// calc.js
function calculateTotal(items, taxRate) {
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);
  return subtotal * (1 + taxRate);
}
 
export default calculateTotal;

The function is defined and named normally in the source file. The export default on the last line exposes it to other modules. This pattern makes the exported name consistent and visible for debugging.

Importing Default Exports

You can import a default export with any name you choose. The original name in the source module does not matter to the importer.

This is both a feature and a potential pitfall. The source module defines the export, and the importer picks the local name:

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

Every one of these import lines works identically because they all point to the same module. The importer alone decides the local name:

javascriptjavascript
import add from "./math.js";
import sum from "./math.js";
import whatever from "./math.js";
 
console.log(add(5, 3));      // 8
console.log(sum(5, 3));      // 8
console.log(whatever(5, 3)); // 8

This flexibility is convenient, but it can also make code harder to trace. If every file imports the same default export under a different name, searching and refactoring become difficult. Pick one consistent name across your entire codebase and stick with it.

Mixing Default and Named Exports

A module can have one default export and any number of named exports. This is common in libraries where the main function is the default and helper utilities are named. The default always comes first in the file and first in the import statement:

javascriptjavascript
// string-utils.js
export default function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
 
export function titleCase(str) {
  return str.split(" ").map(capitalize).join(" ");
}

The consumer imports the default first, then the named helpers after a comma inside the same single import statement line:

javascriptjavascript
import capitalize, { titleCase } from "./string-utils.js";
 
console.log(capitalize("hello"));      // Hello
console.log(titleCase("hello world")); // Hello World

The default import comes first, separated by a comma from the named imports in curly braces. The order matters: default always before named. You can also add as many named exports as you need, each separated by a comma inside the braces.

When to Use Default vs Named Exports

This table summarizes the tradeoffs between the two export styles:

Default ExportNamed Export
How many per moduleExactly oneAny number
Import syntaximport X from (no braces)import { X } from (braces required)
Can rename on importYes, any nameOnly with as
Tree shakingHarder for bundlersEasier for bundlers
IDE autocompleteWeaker (name is arbitrary)Stronger (name is fixed)
Best forSingle-purpose modules, React componentsUtility modules, constants, helper functions

Use a default export when a file represents one clear entity: a React component, a single class, or a main configuration object. Use named exports when a file is a collection of utilities, constants, or types that consumers may want to pick selectively.

Common Mistake: Using Curly Braces with Default Imports

A frequent beginner mistake is wrapping the default import in curly braces. This is the most common source of undefined import errors:

javascriptjavascript
// greet.js
export default function greet(name) {
  return `Hello, ${name}!`;
}
 
// Wrong -- curly braces look for a named export called "greet"
import { greet } from "./greet.js";  // greet is undefined
 
// Correct -- no curly braces for a default import
import greet from "./greet.js";      // greet is the function

If you see undefined after an import, immediately check whether the source module uses export default or a plain export. The import syntax must match exactly. Curly braces tell JavaScript to look for a named export, and if only a default export exists, the named lookup silently returns undefined.

Common Mistake: Default Exporting an Anonymous Expression

You can default export a value directly without declaring it first, but this creates debugging headaches because the exported value has no name:

javascriptjavascript
// config.js -- works but not recommended
export default {
  apiUrl: "https://api.example.com",
  timeout: 5000
};

This works, but the exported object appears as anonymous in stack traces and debugger output. Prefer naming the value first for better debuggability:

javascriptjavascript
// config.js -- clearer and easier to debug
const defaultConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000
};
 
export default defaultConfig;

Now the object has a clear name. When you inspect it in the console or read a stack trace, you see defaultConfig instead of an anonymous object literal.

How Bundlers Handle Default Exports

Under the hood, bundlers like Vite and Webpack treat default exports as named exports with the special internal name default. When you write a default export like this:

javascriptjavascript
export default function greet() {}

The bundler roughly translates it internally to a named export with the reserved internal name default, which is how the module system tracks it:

javascriptjavascript
function greet() {}
export { greet as default };

This internal representation is why you can also import a default export using this less common but valid syntax, which explicitly asks for the default named export and renames it locally. It is rarely needed but useful when dynamically re-exporting modules or introspecting module metadata.

For more on the module system and how it interacts with tree shaking, see the ES6 modules guide.

The { default as greet } pattern explicitly asks for the default named export and renames it locally. You rarely need this, but it is useful when dynamically re-exporting modules.

Rune AI

Rune AI

Key Insights

  • A default export is the one main value a module shares, imported without curly braces.
  • Importers can choose any local name for a default export.
  • A module can have one default export plus any number of named exports.
  • Default exports are common for React components and single-purpose modules.
  • Named exports are preferred for utility modules because they enable tree shaking.
RunePowered by Rune AI

Frequently Asked Questions

Can a module have more than one default export?

No. A module can have exactly one default export. If you try to add two `export default` statements, you will get a syntax error. Use named exports if you need to export multiple values.

Can I import a default export with a different name?

Yes. That is one of the main benefits of default exports. You can import it with any valid identifier name, unlike named exports which must match the exported name unless you use an alias.

Should I prefer default or named exports?

Named exports are generally preferred in larger codebases because they work better with tree shaking, IDE autocompletion, and refactoring tools. Default exports are useful when a module has one clear main purpose, like a React component or a single utility class.

Conclusion

Default exports give each module one primary value to share. They are simple to write, flexible to import, and common in frameworks like React and Next.js. Use them when a module has one clear purpose. For modules with multiple utilities, prefer named exports so consumers can import only what they need.