Skip to content

Writing Hardhat tasks

At its core, Hardhat is a task runner that lets you automate your development workflow. It comes with built-in tasks like compile and test, but you can also add your own custom tasks.

In this guide, we’ll explore how to extend Hardhat’s functionality using tasks. It assumes you’ve initialized a sample project. If you haven’t, read the getting started guide first.

Let’s write a simple task that prints the list of available accounts. This will help you understand how tasks work and how to create your own.

First, copy and paste the following code into your Hardhat config file:

hardhat.config.ts
import { defineConfig, task } from "hardhat/config";
const printAccounts = task("accounts", "Print the accounts")
.setAction(() => import("./tasks/accounts.js"))
.build();

Now let’s create a tasks/accounts.ts file with the task action, which contains the logic that the task will run:

tasks/accounts.ts
import { HardhatRuntimeEnvironment } from "hardhat/types/hre";
interface AccountTaskArguments {
// No argument in this case
}
export default async function (
taskArguments: AccountTaskArguments,
hre: HardhatRuntimeEnvironment,
) {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
}

Next, add the printAccounts task to the exported configuration object in your Hardhat config file:

hardhat.config.ts
export default defineConfig({
// ... rest of the config
tasks: [printAccounts],
});

Now you should be able to run it:

Terminal window
npx hardhat accounts

We’re using the task function to define our new task. Its first argument is the name of the task, which is what we use on the command line to run it. The second argument is the description, which appears when you run hardhat help.

The task function returns a task builder object that lets you further configure the task. In this case, we use the setAction method to define the task’s behavior by providing a function that lazy loads another module.

That module exports the action function itself, which implements your custom logic. In this case, we send a request to the network provider to get all the configured accounts and print their addresses.

Add parameters to your tasks, and Hardhat handles their parsing and validation for you. Override existing tasks to customize how different parts of Hardhat work.

As an alternative to defining task actions in separate files, define them directly inline using setInlineAction. This is convenient for simple tasks where a separate file would be overkill.

Here’s how the accounts task would look using an inline action:

hardhat.config.ts
import { defineConfig, task } from "hardhat/config";
const printAccounts = task("accounts", "Print the accounts")
.setInlineAction(async (taskArguments, hre) => {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
})
.build();
export default defineConfig({
// ... rest of the config
tasks: [printAccounts],
});

With setInlineAction, the task logic is defined directly as a function parameter, eliminating the need for a separate file.

Choosing between setAction and setInlineAction

Section titled “Choosing between setAction and setInlineAction”

Use setAction when you’re building a plugin or a complex task with a lot of code, or if it needs to import dependencies. This keeps the code organized, improves Hardhat’s load time (as they’re loaded on demand), and makes your setup or plugin more resilient to installation errors.

On the other hand, if you’re building a simple task that only uses the Hardhat Runtime Environment, use setInlineAction to define the task’s behavior inline, without the boilerplate of a separate file.

Here’s a comparison of the two approaches:

setAction() (Lazy-loaded)setInlineAction()
Use casesComplex tasks
Plugin tasks
Tasks that import dependencies
Simple user tasks
Available for plugins✅ Yes (required)❌ No
Available for users✅ Yes✅ Yes
PerformanceLazy-loaded on demandLoaded every time you run Hardhat
File organizationSeparate action filesDefined inline in your config
Can use import { ... } from 'hardhat'?✅ Yes, because they are loaded after Hardhat’s initialization❌ No, because they are evaluated during Hardhat’s initialization

Each task must define exactly one action: call either setAction() or setInlineAction(), but not both.

You can also use setInlineAction with overrideTask to customize existing tasks directly in your config file.

To learn more about how task actions are loaded, see the Task Actions’ lifecycle documentation.