JavaScript Default Exports Complete Tutorial

A complete tutorial on JavaScript default exports. Covers the export default syntax for functions, classes, objects, and expressions, how default imports work with any local name, when to prefer defaults over named exports, mixing defaults with named, and common pitfalls to avoid.

JavaScriptintermediate
10 min read

Every ES6 module can have one default export -- a single value that represents the module's primary API. When another file imports from the module without curly braces, it receives this default value. Default exports are the right choice when a module has a clear single responsibility.

Basic Syntax

Exporting a Function

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

Exporting a Class

javascriptjavascript
// User.js
export default class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
 
  display() {
    return `${this.name} <${this.email}>`;
  }
}

Exporting an Expression

javascriptjavascript
// config.js
export default {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3,
};

Exporting an Arrow Function

javascriptjavascript
// double.js
export default (n) => n * 2;

Importing Default Exports

The import name is entirely up to the consumer -- it does not need to match the export name:

javascriptjavascript
import greet from "./greet.js";
import sayHello from "./greet.js"; // same module, different local name
 
import User from "./User.js";
import AppConfig from "./config.js";
import doubler from "./double.js";
 
console.log(greet("Alice"));      // "Hello, Alice!"
console.log(sayHello("Bob"));     // "Hello, Bob!"
console.log(new User("Alice", "a@b.com").display());
console.log(AppConfig.timeout);   // 5000
console.log(doubler(21));         // 42

No curly braces around the import name. Curly braces are for named exports.

Named vs Anonymous Defaults

You can export a named declaration as the default:

javascriptjavascript
// Named default — the name 'Logger' exists inside the module
export default class Logger {
  log(msg) { console.log(msg); }
}

Or export anonymously:

javascriptjavascript
// Anonymous default — no name inside the module
export default class {
  log(msg) { console.log(msg); }
}
ApproachStack TracesSelf-ReferenceRecommended?
Named defaultShows class/function nameCan reference itselfYes
Anonymous defaultShows default or anonymousCannot reference itselfAvoid

Named defaults are preferred because they produce better error messages and stack traces.

Mixing Default and Named Exports

A module can have one default and multiple named exports:

javascriptjavascript
// api.js
export default class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
 
  async get(path) {
    const res = await fetch(`${this.baseUrl}${path}`);
    return res.json();
  }
}
 
export const DEFAULT_BASE_URL = "https://api.example.com";
export const DEFAULT_TIMEOUT  = 5000;
 
export function createClient(options = {}) {
  return new ApiClient(options.baseUrl || DEFAULT_BASE_URL);
}

Importing Both

javascriptjavascript
// Import default + named in one statement
import ApiClient, { DEFAULT_BASE_URL, createClient } from "./api.js";
 
// Or import default separately
import ApiClient from "./api.js";
import { createClient } from "./api.js";

Default Export as a Separate Statement

javascriptjavascript
// utils.js
function formatDate(date) {
  return date.toISOString().split("T")[0];
}
 
// Export existing binding as default
export { formatDate as default };

This is equivalent to export default formatDate but uses the named-export syntax. It is useful when you want to export the same binding as both default and named:

javascriptjavascript
export { formatDate as default, formatDate };

Re-Exporting a Default

javascriptjavascript
// components/index.js (barrel file)
 
// Re-export another module's default as a named export
export { default as Button } from "./Button.js";
 
// Re-export another module's default as THIS module's default
export { default } from "./Layout.js";

See JavaScript ES6 modules import export guide for more on barrel patterns.

When to Use Default Exports

ScenarioUse Default?Why
Module exports one classYesClear single responsibility
Module exports one functionYesConcise import syntax
Module exports config objectYesSingle entry point
Module exports many utilitiesNoNamed exports are better
Library with tree-shakingNoNamed exports are tree-shakable
Team prefers consistent importsNoNamed exports enforce consistent names

The Case Against Default Exports

Some teams (and style guides like the Airbnb guide) prefer named-only exports because:

  1. Consistent naming: Importers must use the exported name, reducing confusion
  2. Refactoring support: IDE renaming tools track named exports reliably
  3. Autocomplete: Editors can suggest named exports from a module; defaults require knowledge of the module
  4. Tree-shaking: Named exports are individually shakable; a default object export is not

See JavaScript named exports a complete tutorial for the named-only approach.

Common Mistakes

Mistake 1: Curly Braces on Default Import

javascriptjavascript
// Wrong — this imports a NAMED export called "User"
import { User } from "./User.js"; // undefined if User only has a default
 
// Correct — no braces for default
import User from "./User.js";

Mistake 2: Multiple Default Exports

javascriptjavascript
// SyntaxError — only one default per module
export default function foo() {}
export default function bar() {}

Mistake 3: Expecting the Import Name to Match

javascriptjavascript
// User.js
export default class User {}
 
// main.js
import Person from "./User.js"; // Works — Person IS User

This is valid but can be confusing. Using the same name as the class helps readability.

Mistake 4: Default Exporting a Variable Declaration

javascriptjavascript
// SyntaxError
export default const x = 5;
 
// Correct alternatives
const x = 5;
export default x;
 
// Or directly
export default 5;

export default expects an expression or a function/class declaration, not a const/let/var declaration.

Dynamic Import of Default Exports

javascriptjavascript
async function loadLogger() {
  const module = await import("./Logger.js");
  const Logger = module.default; // Access default via .default
  return new Logger("app");
}

See dynamic imports in JavaScript complete guide for the full dynamic import tutorial.

Rune AI

Rune AI

Key Insights

  • One default export per module: It represents the module's primary public API value
  • Import name is your choice: import Anything from "./mod.js" -- the name does not need to match the export
  • No curly braces for defaults: import X from (default) vs import { X } from (named) -- a common source of import bugs
  • Named defaults produce better stack traces: Always name your default-exported functions and classes for better debugging
  • Consider named-only for utility modules: Named exports offer better IDE support, consistent naming, and individual tree-shaking
RunePowered by Rune AI

Frequently Asked Questions

Can a module have only named exports and no default?

Yes. A default export is optional. Many utility modules export only named bindings.

Is export default hoisted like function declarations?

`export default function` declarations are hoisted within the module. `export default` followed by an expression is not hoisted.

Can I conditionally set the default export?

Not with static `export default`. The declaration must be at the top level. However, you can conditionally set a variable and export it: `const value = condition ? a : b; export default value;`.

How does default export work with CommonJS interop?

When importing a CommonJS module via ESM, the entire `module.exports` object becomes the default export. Named exports may also be available depending on the bundler or runtime.

Conclusion

Default exports provide a clean import syntax for modules with a single primary value. They work best for classes, components, and configuration objects. However, they sacrifice naming consistency and IDE discoverability compared to named exports. The decision between default and named often comes down to team preference and codebase style. For the named export counterpart, see JavaScript named exports a complete tutorial, and for the overall module system, see JavaScript ES6 modules import export guide.