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.
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
// greet.js
export default function greet(name) {
return `Hello, ${name}!`;
}Exporting a Class
// User.js
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
display() {
return `${this.name} <${this.email}>`;
}
}Exporting an Expression
// config.js
export default {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};Exporting an Arrow Function
// 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:
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)); // 42No 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:
// Named default — the name 'Logger' exists inside the module
export default class Logger {
log(msg) { console.log(msg); }
}Or export anonymously:
// Anonymous default — no name inside the module
export default class {
log(msg) { console.log(msg); }
}| Approach | Stack Traces | Self-Reference | Recommended? |
|---|---|---|---|
| Named default | Shows class/function name | Can reference itself | Yes |
| Anonymous default | Shows default or anonymous | Cannot reference itself | Avoid |
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:
// 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
// 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
// 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:
export { formatDate as default, formatDate };Re-Exporting a Default
// 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
| Scenario | Use Default? | Why |
|---|---|---|
| Module exports one class | Yes | Clear single responsibility |
| Module exports one function | Yes | Concise import syntax |
| Module exports config object | Yes | Single entry point |
| Module exports many utilities | No | Named exports are better |
| Library with tree-shaking | No | Named exports are tree-shakable |
| Team prefers consistent imports | No | Named exports enforce consistent names |
The Case Against Default Exports
Some teams (and style guides like the Airbnb guide) prefer named-only exports because:
- Consistent naming: Importers must use the exported name, reducing confusion
- Refactoring support: IDE renaming tools track named exports reliably
- Autocomplete: Editors can suggest named exports from a module; defaults require knowledge of the module
- 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
// 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
// SyntaxError — only one default per module
export default function foo() {}
export default function bar() {}Mistake 3: Expecting the Import Name to Match
// User.js
export default class User {}
// main.js
import Person from "./User.js"; // Works — Person IS UserThis is valid but can be confusing. Using the same name as the class helps readability.
Mistake 4: Default Exporting a Variable Declaration
// 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
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
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) vsimport { 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
Frequently Asked Questions
Can a module have only named exports and no default?
Is export default hoisted like function declarations?
Can I conditionally set the default export?
How does default export work with CommonJS interop?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.