completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

import & export

Learn how JavaScript modules share named exports, default exports, renamed imports, re-exports, live bindings, and strict module scope without leaking globals.

By the end you can
  • 01
    Share and use valuesExport values from one file and import them in another: named, default, all at once, or under a new name.
  • 02
    Pass exports onUse an index file that passes on exports from other files, and know what happens when two names clash.
  • 03
    Know what an import isExplain why an import shows changes, why you can't assign to it, and why modules are private and strict.

What import and export mean

A module is a JavaScript file that keeps its code to itself. Its variables and functions are private: other files can't see them. A module can choose a few things to share:

  • export marks a value that this file shares.
  • import lets another file use a shared value.

Big websites are not one long file. They are made of hundreds of small files. Each file shares only what other files need and keeps the rest private. That way you can see which file uses what.

One-sentence definition

export shares a name from one file, and import gives another file a read-only name that looks at that shared value.

Modules use ideas from three earlier lessons: Scope & lexical environments, Strict mode, and Hoisting & the temporal dead zone. Open one of them if that idea feels unclear.

Named and default exports

INTERACTIVE

A file can share values in two ways:

  • A named export has a name, such as price. To import it, write that exact name inside curly braces: import { price }.
  • A default export is the file's one main value. Its export name is default. To import it, leave out the curly braces and pick any name you like.

A file can have many named exports, but only one default export.

Real-life analogyA shop shelf and the house special

Picture a small cafe. Every item on the shelf has a label, so you ask for "tea" by name. The cafe also has one house special. You can order it and call it "lunch" or "special" at your table. Everyone still gets the same dish.

In real life: Items on a shelf, each with a label
In JavaScript: Named exports, like tea and size
In real life: Asking for an item by its label
In JavaScript: import { tea } from './shop.js'
In real life: The house special
In JavaScript: The default export, which you import under any name
In real life: Many items, but one house special
In JavaScript: Many named exports, but one default export
shelf.js shares values, and order.js imports themJavaScript
// shelf.jsexport const price = 5;export default "house special";export { price as cost }; // order.jsimport { price, cost } from "shelf";import special from "shelf";import * as shelf from "shelf";console.log(price, cost, special, shelf.default);

The file shelf.js shares three exports:

  • Line 2 makes a named export, price, with the value 5.
  • Line 3 makes the default export, the text "house special".
  • Line 4 shares price a second time, under the name cost.

In order.js, line 7 asks for price and cost by name. Line 8 takes the default export and calls it special. Line 10 prints 5 5 house special house special.

The cart shelf.js holds two exports, price and default. On the left, import { price } asks for price by the same name. On the right, import special takes the default export and gives it the name special.import { price }pricesame nameimport specialspecialany namepricedefaultshelf.js
Line 7 asks for price by its exact export name. Line 8 takes the default export and picks its own name for it, special.

Line 9, import * as shelf, imports everything at once. It gives you one object, shelf, with each export as a property: shelf.price, shelf.cost, and shelf.default. JavaScript calls it a module namespace object. It is read-only: you can't add, change, or delete its properties. It also has no prototype, so it has no inherited methods like toString().

The lab below runs real modules in a small test page inside this page, called an iframe. Pick what the first file exports and how the second file imports it.

Export and import lab: does the import match?
The two files, one after the otherJavaScript
export const tea = "green";export default "house special"; import { tea } from "shop";console.log(tea);
What the test page printedloading

Loading the chosen code in the test page…

Try it yourself

Pick what the file exports and how the other file imports it. The code runs as real modules in a small test page (an iframe).

The lab only runs the code shown here. The test page is sandboxed, which means it is kept apart from this page, and it sends back only what was printed.

Try a mismatch. Choose "Default export only" and import { tea }. The lab shows the real browser error. The browser checks every import before any line of the file runs, so none of the file's code runs.

Renaming with as

CONCRETE

The word as gives an import or an export a new name. On an import, the new name is used only in the importing file. On an export, the new name is the one other files ask for. Either way, the original variable doesn't change. Use as when:

  • two files export the same name,
  • the name would clash with a variable you already have,
  • a clearer name would help.
Real-life analogyYour own sticker on a product

You buy two products that are both labeled "name". At home, you put your own sticker on one: "userName". Now you can tell them apart. The labels in the shop stay the same.

In real life: The package says cost
In JavaScript: The exporting file shares cost
In real life: You add your own sticker that says price
In JavaScript: import { cost as price }
In real life: The shop's label stays the same
In JavaScript: Other files still ask for cost
Rename on export, then again on importJavaScript
// prices.jsconst cost = 12;export { cost as priceInDollars }; // cart.jsimport { priceInDollars as itemPrice } from "./prices.js";const price = "shown in the UI";console.log(itemPrice, price);

Line 3 shares cost under a new name, priceInDollars. Line 6 asks for priceInDollars and calls it itemPrice in this file. That leaves the name price free for line 7. Line 8 prints 12 shown in the UI.

Read priceInDollars as itemPrice as: "ask for priceInDollars, then call it itemPrice here." You can rename on export, on import, or both.

Passing exports on

INTERACTIVE

A file can pass on exports from other files. This is called a re-export. A file that mostly re-exports is called a barrel file, and it is usually named index.js. Other files can then import from this one place, instead of from each file one by one.

Real-life analogyA reception desk in an office building

In an office building, reception sends you on to the right office. Reception makes your visit easier. It doesn't do the work of every office itself.

In real life: The reception desk
In JavaScript: index.js, the barrel file
In real life: Sending you on to the math office
In JavaScript: export * from './math.js'
In real life: Sending you to one person in another office
In JavaScript: export { shout } from './strings.js'
In real life: Handing you the list of one whole office
In JavaScript: export * as strings from './strings.js'
index.js passes on exports from two filesJavaScript
// math.jsexport const add = (a, b) => a + b;export const name = "math"; // strings.jsexport const shout = (text) => text.toUpperCase();export const name = "strings"; // index.jsexport * from "math";export { shout } from "strings";export * as strings from "strings"; // app.jsimport { add, shout, strings } from "index";console.log(add(2, 3), shout("ok"), strings.name);

The three lines in index.js each pass on exports in a different way:

  • Line 10, export * from, passes on every named export of math.js.
  • Line 11 passes on just one export of strings.js: shout.
  • Line 12 passes on all of strings.js as one object, named strings.

Line 15 imports all three from index.js. Line 16 prints 5 OK strings.

Three boxes on the left and middle: math.js holds add, strings.js holds shout, and index.js is empty. Arrows go from math.js and strings.js into index.js, then from index.js to app.js, which holds add and shout.addmath.jsshoutstrings.jsindex.jsforwards only{ add, shout }addshoutapp.js
index.js keeps nothing of its own. It passes add and shout on, so app.js can import both from one place.

Two rules to know about export * from:

  • It skips the default export. It passes on named exports only.
  • If two files export the same name, it leaves that name out, because JavaScript can't tell which one you mean. For example, math.js and strings.js both export name. If index.js used export * from on both, name would be left out. To pick one yourself, write export { name } from './file.js';.

The lab below shows both of these, plus one more mistake.

Barrel lab: one index.js, two files behind it
index.js, then app.jsJavaScript
export * from "math";export { shout } from "strings";export * as strings from "strings"; import { add, shout, strings } from "index";console.log(add(2, 3), shout("ok"), strings.name);
What the test page printedloading

Loading the chosen code in the test page…

Try it yourself

index.js passes on names from the two files. app.js imports them all from this one place.

The test page runs math, strings, index, and app as four real modules. This page only accepts messages from that test page.

Setup files and JSON files

MODULE PATTERNS

Some imports don't bring in a value at all. import "./setup.js"; only runs the file. This is called a side-effect import. A side effect is anything a file does when it runs, like printing a message or changing the page. Use this import for files that set something up, such as:

  • a theme stylesheet,
  • a file that registers a custom HTML element,
  • a file that adds one event listener.

This import gives you no names to use. If you also need a value from the file, add a normal named or default import.

Run setup, then import the values you needJavaScript
// setup.jsconsole.log("setup runs");export const theme = "light"; // button.jsexport default "Button"; // index.jsexport { default } from "./button.js"; // app.jsimport "./setup.js";import { theme } from "./setup.js";import Button from "./index.js";console.log(theme, Button);

Line 12 runs setup.js, so line 2 prints setup runs first. Line 13 imports theme by name from the same file. The file does not run again, because a module runs only once. Line 14 gets the default export of button.js through index.js. Line 15 prints light Button.

Pass on a default export by name

export * from skips the default export. To pass it on, write export { default } from './button.js';, as line 9 does. Then anyone reading index.js can see that the default export is passed on.

Import a JSON fileJavaScript
// settings.json{ "theme": "light", "pageSize": 3 } // app.mjsimport settings from "./settings.json" with { type: "json" };console.log(settings.theme, settings.pageSize);

Line 5 imports a JSON file. The part with { type: 'json' } is called an import attribute. It tells JavaScript to read the file as JSON data, not as code. Line 6 prints light 3 in Node 22.

Browsers added JSON imports at different times, so check that the browsers you need support them. Older code may write assert instead of with. That spelling is out of date.

Imports look into the file

STEP THROUGH

An import is not a copy of a value. It is a name that looks into the exporting file. When that file changes the value, the import shows the new value. This is called a live binding. A binding is a name joined to a value, like a variable. You can read a live binding, but you can't assign to it.

Real-life analogyA window, not a photo

A photo shows one moment and never changes. A window shows what is happening right now. An import works like a window.

In real life: Looking through a window into a room
In JavaScript: Reading an imported name
In real life: Someone in the room moves a chair
In JavaScript: The exporting file changes count
In real life: You see the chair in its new spot
In JavaScript: The import shows the new value
In real life: You can't move the chair through the glass
In JavaScript: The importing file can't assign to the import

In the step-through below, counter.js exports count and a function, increment(), that adds 1 to it. Then app.js imports both.

Step through: does the import see the change?
Step 0 of 8Ready
Your turn: follow the blue line

app.js reads count, calls increment(), reads count again, then tries to change it. Step through each line.

Running in
  1. script
Next: line 2
// counter.jsexport function increment() {  count += 1;} // app.jsimport { count, increment } from "counter";console.log(count);increment();console.log(count);count = 10;
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose line 9 before you step

Changing the line starts a new run from the top.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Now choose "Copy first" above and step through again. This time, line 9 copies count into a normal variable, snapshot, before increment() runs.

After increment runs. Left: the import tag count points into the count box in counter.js, which now holds 1, so console.log(count) prints 1. Right: the snapshot box in app.js holds the copy 0, so console.log(snapshot) prints 0.console.log(count)prints 1countimport in app.js1countin counter.jsconsole.log(snapshot)prints 00snapshotcopy in app.js
After increment() runs, the import count looks into counter.js and sees the new 1. snapshot is a copy made before the change, so it still holds 0.

counter.js made count with export let, so counter.js may change it. In app.js, the name count is read-only. To change the value from app.js, call a function that counter.js exports for that job, like increment().

Two files that import each other

Two files can import each other. This is called a circular import. It can work, because imports look into the other file. But one file may read a name before the other file has set it. Then you get the same error as reading a let or const before its line runs. That waiting time is called the temporal dead zone. The Hoisting & TDZ lesson explains it.

Two files that import each otherJavaScript
// a.jsimport { b } from "./b.js";export const a = "A"; // b.jsimport { a } from "./a.js";console.log(a); // may throw if a is still uninitializedexport const b = "B";

Say a.js is loaded first. Its import on line 2 makes b.js run before the rest of a.js. Line 7 in b.js then reads a, but line 3 has not run yet. So the read throws a ReferenceError.

Modules are private and strict

INTERACTIVE

A module is not just an old-style script with new words. Modules follow different rules:

  • They always use strict mode. So assigning to a name you never declared throws an error.
  • Variables at the top of a module stay private to that module.
  • At the top of a module, this is undefined.
  • Each module file runs only once, however many times it is imported.

The lab below checks all four rules in a real browser module.

Module rules lab: strict, private, runs once
visits.js checks the module rulesJavaScript
// visits.jsconsole.log(this === undefined);let visits = 0;visits += 1;try { leaked = "global"; }catch (error) { console.log(error.name); }export const visitsSeen = visits;export const globalHasVisits = "visits" in globalThis; // app.jsimport { visitsSeen, globalHasVisits } from "visits";import { visitsSeen as again } from "visits";console.log(visitsSeen, again, globalHasVisits);
What the test page printedloading

Loading the chosen code in the test page…

Try it yourself
Reset loads the test page again.

Top-level this is undefined. Assigning to a name that was never declared throws. visits is not on globalThis. And importing the same file twice shares its one run.

This is a real browser module, running in a small sandboxed test page.

The lab prints three lines:

  • Line 2 prints true, because this is undefined.
  • Line 5 assigns to leaked, which was never declared. It throws, and line 6 prints ReferenceError.
  • Line 13 prints 1 1 false. app.js imports visits.js twice, but the file ran once, so both names show 1. And visits is not on globalThis.

These rules fix many problems from the var and the global object lesson. A let visits at the top of a module stays private. It never becomes globalThis.visits.

Where you will use this

SORTER

Real projects use all of these forms. Here is a simple way to choose:

  • Named exports for a file with several tools, like price helpers.
  • A default export for a file with one main value, like a Button component.
  • Re-exports in an index.js that gives a folder one place to import from.

Whichever style you pick, use it the same way across the whole project. Then everyone can read it quickly.

Each row of the table below is one form. Read across to see how you import it and when it fits.

Export forms and when to use each
Export formHow you import itWhen to use it
Named exportimport { price } from './shop.js'The file shares several values, each with a clear name.
Default exportimport anything from './shop.js'The file shares one main value.
Import everythingimport * as shop from './shop.js'You want all of a file's exports on one read-only object.
Re-exportexport { price } from './shop.js'You want to pass on another file's export.
Which export form fits?
  • A helper file has three tools: parsePrice, formatPrice, and taxRate.
  • A file holds one main thing: a Button component.
  • features/cart/index.js passes on the cart tools that other files may use.
  • A checks file shares isEmail and isStrongPassword.
  • A theme file exports the one theme object the app uses.
  • ui/index.js collects Button, Modal, and Toast from their own files.
Try it yourself
0 of 6 correct

Sort each file into the export form that fits it best.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

Each row of this table is a common mistake, next to what is really true.

Four common mistakes about modules
The mistakeWhat is really true
A default export has no name.Its name is default. The importing file picks its own name for it.
An import is a copy.An import looks into the exporting file, and you can't assign to it.
A re-exporting file owns the values.It only passes them on. It gets no variables of its own.
A module is just an old script.A module is strict and private. In a page, it waits until the HTML is read, and it runs only once.
  • "A default export has no name." Its export name is default. Only the name in the importing file is up to you.
  • "Imports are hoisted, so I can write them anywhere." Imports do run before the rest of the file. That is called hoisting. But you must still write them at the top level of the module, not inside a function or an if.
  • "import * as gives a normal object." It gives a read-only object with no prototype. You can't add or change its properties.
  • "A barrel file always helps." It gives one place to import from. But many barrel files make it hard to find where code really lives.
  • "Modules only matter in browsers." Browsers, Node, and many other JavaScript tools use modules. Each one decides how to find a file from the text after from.

Practice exercises

4 EXERCISES
Exercise 1 · Warm-upPredict a named and a default value

This object stands in for a file's exports. Predict exactly what the code prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const module = { price: 5, default: "house special" };
const { price } = module;
const special = module.default;
console.log(price + " / " + special);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix a renamed import

    The cart file wants a variable named price, but prices.js only exports cost. Type the fixed import line.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    // prices.js
    export const cost = 12;
    
    // cart.js
    import { price } from "./prices.js"; // fix this line

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeLive or copied?

      This code acts like an import that looks into its file. Write the two printed lines, separated by a comma.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let count = 0;
      function increment() { count += 1; }
      console.log(count);
      increment();
      console.log(count);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeAssign to a name that was never declared

        In a module, code runs leaked = "global", but leaked was never declared. Which error name appears?

        Answer, then press Check. Spacing and letter case don’t matter.

          Check your understanding

          8 QUESTIONS
          Lesson quiz · 8 questionsScore: first tries count
          1. Question 1 of 8What is a default export?

            Choose an answer to see the explanation.

          2. Question 2 of 8This object stands in for a file's exports. What does the code print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const module = { price: 5, default: "house special" };
            const { price } = module;
            const special = module.default;
            console.log(price + " / " + special);

            Choose an answer to see the explanation.

          3. Question 3 of 8Why write import { cost as price }?

            Choose an answer to see the explanation.

          4. Question 4 of 8This code acts like an import that looks into its file. What does it print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            let count = 0;
            function increment() { count += 1; }
            console.log(count);
            increment();
            console.log(count);

            Choose an answer to see the explanation.

          5. Question 5 of 8A file imports count and then runs count = 10. What happens?

            Choose an answer to see the explanation.

          6. Question 6 of 8A file uses export * from on two files. Both export a name. What happens to name?

            Choose an answer to see the explanation.

          7. Question 7 of 8Which line passes on another file's default export?

            Choose an answer to see the explanation.

          8. Question 8 of 8Which sentence about modules is true?

            Choose an answer to see the explanation.

          Key takeaways

          • Import a named export by its exact name, inside curly braces. A default export is the export named default; you pick its name when you import it.
          • as gives an import or an export a new name. The other file doesn't change.
          • A re-export passes on another file's exports. export * from skips the default export and leaves out names that two files share.
          • An import looks into the exporting file. It shows changes, and you can't assign to it.
          • Modules use strict mode, keep their variables private, and run only once.

          Final definition.
          import and export connect module files. Each import looks at a value that another file shares, and each file keeps everything else private.

          Up next: Dynamic import & top-level await.

          CompleteFrontend Clear concepts. Working examples.