> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nrwl/nx/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Executors

> Write custom Nx executors to standardize build, test, and deployment scripts with schema validation, caching support, and --help output.

Executors are functions that run a task — a build, a test run, a deployment step, or anything else that can be scripted. Defining a task as an executor gives you:

* **Schema validation** for options, with `--help` output in the terminal.
* **Nx caching** — Nx can cache executor outputs and replay them on cache hits.
* **Composability** — executors can invoke other executors via `runExecutor`.
* **Nx Console integration** — options are rendered as a form in the editor.

## Creating an executor

<Steps>
  <Step title="Add the plugin package if you don't have one yet">
    ```bash theme={null}
    nx add @nx/plugin
    nx g @nx/plugin:plugin tools/my-plugin
    ```
  </Step>

  <Step title="Scaffold a new executor">
    ```bash theme={null}
    nx generate @nx/plugin:executor tools/my-plugin/src/executors/echo
    ```

    This creates the following files:

    ```
    tools/my-plugin/src/executors/echo/
    ├── executor.spec.ts   # Unit tests
    ├── executor.ts        # Implementation
    ├── schema.d.ts        # TypeScript interface for options
    └── schema.json        # JSON Schema for validation
    ```
  </Step>
</Steps>

## Defining the schema

`schema.json` describes the options your executor accepts:

```json theme={null}
// tools/my-plugin/src/executors/echo/schema.json
{
  "$schema": "https://json-schema.org/schema",
  "type": "object",
  "properties": {
    "textToEcho": {
      "type": "string",
      "description": "The text to echo to the console"
    }
  },
  "required": ["textToEcho"]
}
```

The TypeScript interface in `schema.d.ts` mirrors the JSON schema:

```typescript theme={null}
// tools/my-plugin/src/executors/echo/schema.d.ts
export interface EchoExecutorOptions {
  textToEcho: string;
}
```

## Executor function signature

Every executor exports a default async function that receives the resolved options and an `ExecutorContext`. It must return `Promise<{ success: boolean }>`.

```typescript theme={null}
// tools/my-plugin/src/executors/echo/executor.ts
import type { ExecutorContext } from '@nx/devkit';
import { exec } from 'child_process';
import { promisify } from 'util';

export interface EchoExecutorOptions {
  textToEcho: string;
}

export default async function echoExecutor(
  options: EchoExecutorOptions,
  context: ExecutorContext
): Promise<{ success: boolean }> {
  console.info(`Executing "echo"...`);
  console.info(`Options: ${JSON.stringify(options, null, 2)}`);

  const { stdout, stderr } = await promisify(exec)(
    `echo ${options.textToEcho}`
  );
  console.log(stdout);
  console.error(stderr);

  const success = !stderr;
  return { success };
}
```

## Accessing project information via context

The `ExecutorContext` object contains the full project graph and the name of the project and target being run:

```typescript theme={null}
import type { ExecutorContext } from '@nx/devkit';

export default async function myExecutor(
  options: MyExecutorOptions,
  context: ExecutorContext
): Promise<{ success: boolean }> {
  // The name of the project being built
  const projectName = context.projectName;

  // Full project configuration (root, sourceRoot, targets, tags, etc.)
  const projectConfig = context.projectsConfigurations.projects[projectName];
  const projectRoot = projectConfig.root;

  // The full Nx project graph
  const graph = context.projectGraph;

  // Dependencies of this project
  const deps = graph.dependencies[projectName] ?? [];

  console.log(`Building ${projectName} at ${projectRoot}`);
  console.log(`Has ${deps.length} dependencies`);

  return { success: true };
}
```

## Registering an executor in project.json

Add the executor to the relevant project's `targets` block in `project.json`:

```json theme={null}
// apps/my-app/project.json
{
  "name": "my-app",
  "targets": {
    "echo": {
      "executor": "@myorg/my-plugin:echo",
      "options": {
        "textToEcho": "Hello World"
      }
    }
  }
}
```

<Warning>
  Use the `name` field from `tools/my-plugin/package.json` when referencing your plugin — not the folder path.
</Warning>

Then run the executor:

```bash theme={null}
nx run my-app:echo
```

Expected output:

```
Executing "echo"...
Options: {
  "textToEcho": "Hello World"
}
Hello World
```

## Composing executors with runExecutor

Executors can invoke other executors. This is useful when a task is a combination of multiple steps:

```typescript theme={null}
// tools/my-plugin/src/executors/serve-all/executor.ts
import { ExecutorContext, runExecutor } from '@nx/devkit';

export interface MultipleExecutorOptions {}

export default async function serveAllExecutor(
  options: MultipleExecutorOptions,
  context: ExecutorContext
): Promise<{ success: boolean }> {
  // Run both servers concurrently; stop when the first one finishes
  const result = await Promise.race([
    await runExecutor(
      { project: 'api', target: 'serve' },
      { watch: true },
      context
    ),
    await runExecutor(
      { project: 'web-client', target: 'serve' },
      { watch: true },
      context
    ),
  ]);

  for await (const res of result) {
    if (!res.success) return res;
  }

  return { success: true };
}
```

## Custom hashers

By default, Nx hashes all files in a project to determine cache validity. If your executor only depends on a subset of files, or on external data not in the project, you can provide a custom hasher.

Generate an executor with a hasher included:

```bash theme={null}
nx g @nx/plugin:executor tools/my-plugin/src/executors/my-executor --includeHasher
```

Or add a hasher manually by creating `hasher.ts` and registering it in `executors.json`:

```json theme={null}
// tools/my-plugin/executors.json
{
  "executors": {
    "my-executor": {
      "implementation": "./src/executors/my-executor/executor",
      "hasher": "./src/executors/my-executor/hasher",
      "schema": "./src/executors/my-executor/schema.json"
    }
  }
}
```

The hasher receives the full `Task` and a `HasherContext`:

```typescript theme={null}
// tools/my-plugin/src/executors/my-executor/hasher.ts
import { CustomHasher, Task, HasherContext } from '@nx/devkit';

// This hasher delegates to the default Nx algorithm
export const myHasher: CustomHasher = async (
  task: Task,
  context: HasherContext
) => {
  return context.hasher.hashTask(task);
};

export default myHasher;
```

<Warning>
  A custom hasher **replaces** the default hashing — it does not extend it. If the hash never changes, every run of that target will be a cache hit. Make sure all inputs that affect the executor's output are included in your hash.
</Warning>

## Key devkit utilities for executors

| API                                     | Description                                                                                       |
| --------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `ExecutorContext`                       | Context object passed to every executor; contains project graph, project name, and workspace root |
| `runExecutor(target, options, context)` | Invoke another executor from within an executor                                                   |
| `parseTargetString(str)`                | Parse a `project:target:configuration` string into a `Target` object                              |
| `CustomHasher`                          | Type for a custom hash function                                                                   |
| `HasherContext`                         | Context passed to a custom hasher                                                                 |
