Skip to main content
Extension.js targets the browser runtime by default. It does not automatically polyfill Node core modules, which keeps bundles small. If a dependency reaches for buffer, stream, or crypto, you see a resolution error at build time. Below you can learn when to add polyfills and when to choose a browser-native alternative.

When Node polyfills are a good fit

  • A required dependency needs Node core modules in browser runtime.
  • You are incrementally migrating code from Node-centric packages.
  • You can accept larger bundles for specific runtime capabilities.

Template examples

new-typescript

new-typescript template screenshot Start from a TypeScript baseline when your extension needs explicit bundler/polyfill tuning.
npx extension@latest create my-extension --template=new-typescript
Repository: extension-js/examples/new-typescript

content-typescript

content-typescript template screenshot Use a content-script TypeScript base when Node-dependent libraries run inside page-injected flows.
npx extension@latest create my-extension --template=content-typescript
Repository: extension-js/examples/content-typescript

new-crypto

new-crypto template screenshot See Node crypto polyfill usage in a new-tab extension.
npx extension@latest create my-extension --template=new-crypto
Repository: extension-js/examples/new-crypto

Default behavior

  • Build target is browser-first (web).
  • Resolution prioritizes browser exports (browser, module, main).
  • Extension.js disables Node core fallbacks (for example, crypto, path, and fs) by default.
This means importing Node APIs may fail unless you add explicit fallbacks.

Setting up Node polyfills

Use extension.config.js (or .mjs / .cjs) to extend the Rspack configuration and define safe fallbacks.
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'
import {createRequire} from 'node:module'

const require = createRequire(import.meta.url)

export default {
  config: (config) => {
    config.resolve = config.resolve || {}
    config.resolve.fallback = {
      ...(config.resolve.fallback || {}),
      crypto: require.resolve('crypto-browserify'),
      path: require.resolve('path-browserify'),
      fs: false
    }
    config.plugins = config.plugins || []
    config.plugins.push(new NodePolyfillPlugin())
    return config
  }
}

Install optional polyfill packages

Extension APIs vs Node APIs

Setting polyfill: true in CLI/config enables webextension-polyfill, a compatibility layer that gives Chromium browsers access to the browser.* API namespace. It does not enable Node core module polyfills. Use Node polyfills only for libraries that cannot run with standard Web APIs.

Caveats

  • Do not assume filesystem access in extension runtime; keep fs: false unless you have a very specific browser-safe strategy.
  • Prefer Web Crypto (crypto.subtle) over large Node crypto shims when possible.
  • Polyfills (browser-compatible replacements for Node APIs) increase bundle size and can affect startup time in extension pages and content scripts.
  • Some packages using node: specifiers may need explicit fallback handling.

Next steps

Video walkthrough