Skip to main content
Author extension code with modern import/export syntax across all extension contexts. Extension.js supports ECMAScript Modules (ESM) in background scripts, content scripts, and extension pages. It uses the default Rspack pipeline for module resolution.

When ESM is a good fit

  • You want consistent module syntax across extension and web code.
  • You are sharing code with modern ESM-first packages.
  • You need cleaner tree-shaking (automatic removal of unused code) and explicit dependency boundaries.

Template examples

new

new template screenshot Start a new-tab extension with modern module syntax and minimal setup.
Repository: extension-js/examples/new

content

content template screenshot Inject script logic into page content while keeping ESM-style authoring.
Repository: extension-js/examples/content

Usage with an existing extension

You can use ESM syntax directly in extension source files (.js, .mjs, .ts, .tsx, etc.) without custom bundler setup. If your Node.js project configuration files should also run as ESM, set "type": "module" in package.json. This applies to custom scripts and configuration conventions:

Manifest and service worker notes

For Manifest V3 background workers:
  • Set background.type to "module" when you want native ES module loading in the service worker.
  • Without module worker type, Extension.js uses classic worker loading (bundled scripts without native module syntax).

ESM vs CommonJS reminders

When writing ESM modules:

Include file extensions in relative imports

Interoperability with non-ESM modules can differ

When importing CommonJS packages from ESM, follow the compatibility guidance each package provides.

CommonJS globals are not available in strict ESM contexts

Avoid relying on require, module.exports, __filename, and __dirname inside ESM modules.

Handling environment variables in ECMAScript modules

Extension.js supports both:
  • process.env.EXTENSION_PUBLIC_*
  • import.meta.env.EXTENSION_PUBLIC_*
Use EXTENSION_PUBLIC_ for variables you want available in extension code.

Do imports need mandatory file extensions?

Node.js requires explicit file extensions in ESM import specifiers (import "./util.js", never import "./util"). That rule applies to code Node runs directly, and it confuses people coming to extensions from server code. In Extension.js the bundler resolves imports, not Node, so both styles work in extension code:
Two places the Node rule still matters:
  • Scripts your package.json runs with Node directly (build helpers, codegen): extensions are mandatory there.
  • .mjs files shared between Node tooling and extension code: write extensions so both resolvers accept them.

Next steps

Video walkthrough