---
title: Pipelines
description: Pipelines with Turborepo specify task relationships explicitly and conventionally.
---

import Callout from "../../../components/Callout";
import HeartIcon from "@heroicons/react/solid/HeartIcon";

# Pipelining Package Tasks

In traditional monorepo task runners, like `lerna` or even `yarn`'s own built-in `workspaces run` command, each npm lifecycle script like `build` or `test` is run [topologically](../glossary#topological-order) (which is the mathematical term for "dependency-first" order) or in parallel individually. Depending on the dependency graph of the monorepo, CPU cores might be left idle—wasting valuable time and resources.

Turborepo gives developers a way to specify task relationships explicitly and conventionally. The advantage here is twofold.

1. Incoming new developers can look at the Turborepo `pipeline` and understand how tasks are related.
2. `turbo` can use this explicit declaration to perform an optimized and scheduled execution based on the abundant availability of multi-core processors.

To give you a sense of how powerful this can be, the below diagram compares the `turbo` vs `lerna` task execution timelines:

![Turborepo vs. Lerna Task Execution](/images/docs/turbo-vs-lerna-execution.png)

Notice that `turbo` is able to schedule tasks efficiently--collapsing waterfalls--whereas `lerna` can only execute one task a time. The results speak for themselves.

## Defining a `pipeline`

To define your monorepo's task dependency graph, use the [`pipeline`](../reference/configuration#pipeline) key in the `turbo.json` configuration file at the root of your monorepo. `turbo` interprets this configuration and conventions to properly schedule, execute, and cache the outputs of the tasks in your monorepo.

Each key in the [`pipeline`](../reference/configuration#pipeline) object is the name of a task that can be executed by [`turbo run`](../reference/command-line-reference#turbo-run-task1-task2-1). You can specify its dependencies with the [`dependsOn`](../reference/configuration#dependson) key beneath it as well as some other options related to [caching](./caching).

Packages that do not have this task defined in their `package.json`'s list of `scripts` will be ignored and skipped over during `turbo run`.

An example Pipeline configuration in `turbo.json`:

```json
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": [],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    },
    "lint": {
      "outputs": []
    },
    "deploy": {
      "dependsOn": ["build", "test", "lint"],
      "outputs": []
    }
  }
}
```

The rough execution order for a given package based on the `dependsOn` keys above will be:

1. `build` once its dependencies have run their `build` commands
2. `test` once its _own_ `build` command is finished
3. `lint` whenever
4. `deploy` once its _own_ `build`, `test`, and `lint` commands have finished

The full pipeline can then be run:

```bash
npx turbo run build test lint deploy
```

Turborepo will then efficiently schedule execution minimizing idle CPUs.

## Task Dependency Format

What you are declaring here in the `pipeline` object of the `turbo` configuration is a dependency graph of tasks. In the above example, in plain english, the configuration translates to the following conventions:

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      // A package's `build` task depends on that package's
      // topological dependencies' and devDependencies'
      // `build` tasks  being completed first
      // (that's what the `^` symbol signifies).
      "dependsOn": ["^build"]
    },
    "test": {
      // A package's `test` task depends on that package's
      // own `build` task being completed first.
      "dependsOn": ["build"],
      "outputs": [],
      // A package's `test` task should only be rerun when
      // either a `.tsx` or `.ts` file has changed.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
    },
    "lint": {
      // A package's `lint` task has no dependencies and
      // can be run whenever.
      "outputs": []
    },
    "deploy": {
      // A package's `deploy` task depends on the `build`,
      // `test`, and `lint` tasks of the same package
      // being completed.
      "dependsOn": ["build", "test", "lint"],
      "outputs": []
    }
  }
}
```

### Topological Dependency

The `^` symbol explicitly declares that the task has a [package-topological](../glossary#topological-order-1) dependency on another task.

A common pattern in many TypeScript monorepos is to declare that a package's `build` task (e.g. `tsc`) should only run once the `build` tasks of all of its dependencies in the monorepo have run their own `build` tasks. This type of relationship can be expressed as follows:

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      // "A package's `build` command depends on its dependencies'
      // and devDependencies' `build` commands being completed first"
      "dependsOn": ["^build"]
    }
    // ... omitted for brevity
  }
}
```

### Empty Dependency List

An empty dependency list (`dependsOn` is either undefined or `[]`) means that a task can start executing whenever it can! After all, it has NO dependencies.

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    // ... omitted for brevity

    "lint": {
      // A package's `lint` command has no dependencies and can be run at
      // whenever.
      "outputs": []
    }
  }
}
```

### Restricting Inputs to a Task

A package is considered to have been updated when any of the files in that package have changed.
However, for some tasks, we only want to rerun that task when relevant files have changed.
Specifying `inputs` lets us define which files are relevant for a particular task:

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    // ... omitted for brevity

   "test": {
      // A package's `test` task depends on that package's
      // own `build` task being completed first.
      "dependsOn": ["build"],
      "outputs": [],
      // A package's `test` task should only be rerun when
      // either a `.tsx` or `.ts` file has changed.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    }
  }
}
```

In this scenario, the `test` will only be rerun if a `.tsx` or `.ts` file has changed in the package.

### Tasks that are in the `pipeline` but not in SOME `package.json`

Sometimes tasks declared in the `pipeline` are not present in all packages' `package.json` files. `turbo` will automatically and gracefully ignore those. No problem!

### `pipeline` tasks are the only ones that `turbo` knows about

`turbo` will only account for tasks declared in the `pipeline` configuration. If it's not listed there, `turbo` will not know how to run them.

### Implicit Dependencies and Specific Package Tasks

Sometimes you need to manually place a package-task dependency on another package-task. This can occur especially in repos that are just coming off of a `lerna` or `rush` repository where the tasks are traditionally run in separate phases. Sometimes assumptions are made for those repositories that are not expressible in the simple task `pipeline` configuration, as seen above.
Additionally, you may need to express implicit dependencies or sequences between applications or microservices when using `turbo` in CI/CD.

For these cases, you can express these relationships in your `pipeline` configuration like this:

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": [],
    },
    "deploy": {
      "dependsOn": ["test", "build"],
      "outputs": []
    },
    "frontend#deploy": {
      "dependsOn": ["ui#test", "backend#deploy", "backend#health-check"],
      "outputs": []
    }
  }
}
```

In this example, we illustrate a `deploy` script of a `frontend` application depends on both the `deploy` and `health-check` npm scripts of `backend` as well as the `test` script of a `ui` package. The syntax is `<package>#<task>`.

This seems like it goes against the `"test": { "dependsOn": ["build"] }` and `"deploy": { "dependsOn": ["test"] }`, but it does not. Since `test` and `deploy` scripts do not have topological dependencies (e.g. `^<task>`), they theoretically can get triggered anytime once their own package's `build` and `test` scripts have finished! Unless they are being used for applications/services for deployment orchestration, the general guidance is to get rid of these specific package-task to package-task dependencies in the pipeline as quickly as possible (so the builds can be optimized better).

<Callout>
  **Note: Package-tasks do not inherit cache configuration. You must redeclare
  [`outputs`](../reference/configuration#outputs) at the moment.**
</Callout>

### Running tasks defined in the monorepo's root `package.json` file

`turbo` can run tasks that exist in the `package.json` file at the root of the monorepo.
These must be added to the pipeline configuration using the form `"//#<task>": {...}`. This is
true even for tasks that already have their own entry. For example, if you already have `"build": {...}`
in your pipeline, but want to include the `build` script defined in the monorepo's root `package.json` file when running
`turbo run build`, you must opt the root into it by also including `"//#build": {...}` in your configuration.
Conversely, you _do not_ need to define a generic `"my-task": {...}` entry if all you need is `"//#my-task": {...}`.

A sample pipeline that defines the root task `format` and opts the root into `test` might look like:

```jsonc
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": [],
    },
    // This will cause the "test" script to be included when
    // "turbo run test" is run
    "//#test": {
      "dependsOn": [],
      "outputs": []
    },
    // This will cause the "format" script in the root package.json
    // to be run when "turbo run format" is run. Since the general
    // "format" task is not defined, only the root's "format" script
    // will be run.
    "//#format": {
      "dependsOn": [],
      "outputs": ["dist/**/*"],
      "inputs": ["version.txt"]
    },
  }
}
```

**A note on recursion**: Scripts defined in the monorepo's root `package.json` often call `turbo` themselves.
For example, the `build` script might be `turbo run build`. In this situation, including `//#build` in
`turbo run build` will cause infinite recursion. It is for this reason that tasks run from the monorepo's root must
be explicitly opted into via including `//#<task>` in the pipeline configuration. `turbo` includes
some best-effort checking to produce an error in the recursion situations, but it is up to you to to only
opt in those tasks which don't themselves trigger a `turbo` run that would recurse.

<Callout type="idea" icon={<HeartIcon  className="h-5 w-5 mt-1 text-gray-400" aria-hidden="true" />}>
Turborepo's Pipeline API design and this page of documentation was inspired by [Microsoft's Lage project](https://microsoft.github.io/lage/docs/Guide/pipeline#defining-a-pipeline).
Shoutout to [Kenneth Chau](https://twitter.com/kenneth_chau) for the idea of fanning out tasks in such a concise and elegant way.
</Callout>
