Migrate your project to ESM
Hardhat 3 is ESM-first, so your project needs to be an ESM package. This guide walks you through converting an existing CommonJS project to ESM, and shows the escape hatches for keeping individual files as CommonJS when that’s easier.
Set your project to ESM
Section titled “Set your project to ESM”Add "type": "module" to your package.json:
npm pkg set type=modulepnpm pkg set type=module# yarn doesn't have a command for thisnpm pkg set type=moduleFrom this point on, every .js file in your project is treated as an ES module.
Update your TypeScript config
Section titled “Update your TypeScript config”If you have a tsconfig.json, set compilerOptions.module to an ESM-compatible value like "node20":
{ "compilerOptions": { "module": "node20" }}Convert source files to ESM
Section titled “Convert source files to ESM”This guide covers the conversion in depth, but the rules you’ll hit most often are:
- Replace
const x = require("x")withimport x from "x". - When importing a relative path, you must include the file extension.
const x = require("./x")becomesimport x from "./x.js", notimport x from "./x". - Replace
module.exports = xwithexport default x, andmodule.exports.x = 1withexport const x = 1. __dirnameand__filenameare nowimport.meta.dirnameandimport.meta.filename.
For example, a CommonJS script like this:
const path = require("path");const helper = require("./helper");
module.exports = function run() { console.log(path.join(__dirname, helper.name));};becomes:
import path from "path";import helper from "./helper.js";
export default function run() { console.log(path.join(import.meta.dirname, helper.name));}Keeping files as CommonJS
Section titled “Keeping files as CommonJS”If converting a file is impractical, rename it from .js to .cjs. CommonJS files keep working alongside ESM ones, and you don’t have to change their syntax.
We still recommend writing new files in ESM to take advantage of modern JavaScript features and match Hardhat 3’s defaults.
Using Hardhat from a CommonJS file
Section titled “Using Hardhat from a CommonJS file”You can’t require("hardhat") at the top level of a .cjs file, because Hardhat is an ES module. Use a dynamic import from inside an async function instead:
async function main() { const { default: hre } = await import("hardhat"); // use hre}
main();If you’re using Hardhat from a Mocha test, the same pattern applies inside a before hook. See the Mocha tests migration guide for an example.
Troubleshooting
Section titled “Troubleshooting”If a Hardhat command failed with one of the errors below, here’s what’s happening and how to fix it.
__dirname is not defined in ES module scope
Section titled “__dirname is not defined in ES module scope”A file is now being treated as ESM, but it still uses the CommonJS-only __dirname (or __filename).
Replace those with import.meta.dirname and import.meta.filename. See Convert source files to ESM for the other syntax differences you may need to address.
Cannot use import statement outside a module
Section titled “Cannot use import statement outside a module”Node.js is loading a file as CommonJS, but the file uses import syntax.
Either set "type": "module" in package.json so .js files are loaded as ESM (see Set your project to ESM), or rename the file to .mjs.
require() of ES module ...
Section titled “require() of ES module ...”A CommonJS file is trying to require() an ES module. The most common case is require("hardhat") from a .cjs file.
Convert the file that’s calling require() to ESM, or load the ES module with a dynamic await import(). See Using Hardhat from a CommonJS file for the pattern.