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.
Writing a task
Section titled “Writing a task”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:
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:
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:
export default defineConfig({ // ... rest of the config tasks: [printAccounts],});Now you should be able to run it:
npx hardhat accountspnpm hardhat accountsyarn hardhat accountsWe’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.
Using inline actions
Section titled “Using inline actions”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:
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 cases | Complex tasks Plugin tasks Tasks that import dependencies | Simple user tasks |
| Available for plugins | ✅ Yes (required) | ❌ No |
| Available for users | ✅ Yes | ✅ Yes |
| Performance | Lazy-loaded on demand | Loaded every time you run Hardhat |
| File organization | Separate action files | Defined 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.