# Writing Hardhat tasks

Description: How to write custom Hardhat tasks with Hardhat 3

Note: This document was authored using MDX

  Source: https://github.com/NomicFoundation/hardhat-website/tree/main/src/content/docs/docs/guides/writing-tasks.mdx

  Components used in this page:
    - <Run cmd="..."/>: Runs a command in the terminal with npm/pnpm/yarn.
    - :::caution: A warning callout block. Supports custom title `:::caution[Title]` and icon `:::caution{icon="name"}` syntax.

import Run from "@hh/Run.astro";

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](/docs/getting-started) first.

## 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:

```ts
// 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:

```ts
// 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:

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

Now you should be able to run it:

<Run command="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.

## 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:

```ts
// 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`

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 <br /> Plugin tasks <br /> 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.

:::caution[Plugin developers]
If you're developing a plugin, you must use `setAction()` with lazy-loaded modules. The `setInlineAction()` method is only available for user-defined tasks in configuration files.
:::

To learn more about how task actions are loaded, see the [Task Actions' lifecycle](/docs/plugin-development/explanations/lifecycle#task-actions-lifecycle) documentation.

## Returning a result

Task actions can optionally return a `Result` to signal success or failure to the CLI. When a task action returns a failed result, the CLI sets the process exit code to 1. This is useful when you want to indicate failure to scripts or CI pipelines without throwing an exception.

To use this, import the `Result` type from `hardhat/types/utils` and the helper functions from `hardhat/utils/result`:

```ts
// tasks/accounts.ts
import type { HardhatRuntimeEnvironment } from "hardhat/types/hre";
import type { Result } from "hardhat/types/utils";
import { successfulResult, errorResult } from "hardhat/utils/result";

interface AccountTaskArguments {}

export default async function (
  _taskArguments: AccountTaskArguments,
  hre: HardhatRuntimeEnvironment,
): Promise<Result<string[], string>> {
  const { provider } = await hre.network.connect();
  const accounts = await provider.request({ method: "eth_accounts" });

  if (accounts.length === 0) {
    return errorResult("No accounts found");
  }

  return successfulResult(accounts);
}
```

`Result<ValueT, ErrorT>` is a discriminated union:

- `{ success: true; value: ValueT }` - a successful result, carrying a value
- `{ success: false; error: ErrorT }` - a failed result, carrying error information

Use `successfulResult(value?)` and `errorResult(error?)` to create these objects. Both helpers can be called without a parameter, in which case they set `value` or `error` to `undefined`.

If a task action doesn't return a `Result` (for example, it returns `undefined` or any other value), the CLI exit code is left unchanged.

{/* To learn more about tasks, please read [this guide](../advanced/create-task). */}
