Task Dependencies
Turborepo is most powerful when you express how your tasks relate to each other.
We refer to these relationships as "dependencies", but they are not the same as
package dependencies that you install from your package.json files. While Turborepo
does understand your workspaces, it does not automatically draw any
relationships between their tasks, unless you express them in turbo.json via
the dependsOn configuration.
Let's walk through some common patterns on how to make a task depend on other tasks.
From the same workspace
There might be tasks that need to run before other tasks. For instance,
build might need to be run before deploy.
If both tasks are in the same workspace, you can specify the relationship like this:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {},
"deploy": {
// A workspace's `deploy` task depends on the `build` task of the same workspace.
"dependsOn": ["build"]
}
}
}This means that whenever turbo deploy is run, build will also be run
inside the same workspace.
From dependent workspaces
A common pattern in monorepos is to declare that a workspace's build task
should only run once the build tasks of all the workspaces it depends on are
complete.
This can be confusing as it refers to both workspace dependencies
and task dependencies, which are different concepts. Workspace dependencies are dependencies
and devDependencies in package.json, whereas, task dependencies are dependsOn key in turbo.json.
The ^ symbol (called a "caret") explicitly declares that the task depends on the task in a
workspace it depends on.
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
// A workspace's `build` command depends on its dependencies'
// and devDependencies' `build` commands being completed first
"dependsOn": ["^build"],
}
}
}With the configuration above, if an app installs a package from another workspace, the package's
build script will always run before the app's build script.
From arbitrary workspaces
Sometimes, you may want a workspace-task to depend on another
workspace-task. This can be especially helpful for repos migrating from lerna
or rush, where tasks are run in separate phases by default. Sometimes these
configurations make assumptions that cannot be expressed in a simple pipeline
configuration, as seen above. Or you may just want to express sequences of tasks
between applications or microservices when using turbo in CI/CD.
For these cases, you can express these relationships in your pipeline
configuration using the <workspace>#<task> syntax. The example below describes
the deploy script of a frontend application that depends on the deploy and
health-check scripts of backend, as well as the test script of a ui
workspace:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// Explicit workspace-task to workspace-task dependency
"frontend#deploy": {
"dependsOn": ["ui#test", "backend#deploy", "backend#health-check"]
}
}
}This explicit configuration for frontend#deploy may seem to conflict with the
test and deploy task configurations, but it does not. Since test and
deploy do not have dependencies on other workspaces (e.g. ^<task>), they can
execute any time after their workspace's build and test scripts have
finished.
Notes:
- Although this
<workspace>#<task>syntax is a useful escape hatch, we generally recommend using it for deployment orchestration tasks such as health checks, rather than build-time dependencies, so that Turborepo can optimize these tasks more efficiently - Workspace-tasks do not inherit cache configuration. You must redeclare
outputsat the moment. <workspace>must match thenamekey in the workspace'spackage.jsonor the task will be ignored.
No dependencies
An empty dependency list (dependsOn is either undefined or []) means that
nothing needs to run before this task! After all, it has no dependencies.
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// A workspace's `lint` command has no dependencies and can be run any time.
"lint": {}
}
}Dependencies outside of a task
Let's say you have a common ui package that you are using in two apps, docs and web.
apps/
docs/package.json # Depends on ui
web/package.json # Depends on ui
packages/
ui/package.json # No workspace dependencies
turbo.json
package.jsonYou've written some TypeScript in your workspaces and it's time run tsc to check your types.
There are two requirements here:
- All type checks run in parallel to keep things speedy: Because the results of your type checks don't depend on each other, you can run all of them in parallel.
- A change in a dependency should result in a cache miss: If the
uipackage changes, the type checking task indocsorwebshould know to miss cache.
To accomplish this, you'll create a fake, recursive task in your graph and depend on it:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"your-task": {
"dependsOn": ["topo"]
}
}
}Since the topo task doesn't exist in your scripts, Turborepo will complete the task "instantly" and then look to any workspaces that were depending on that workspace. Because of this, your tasks will execute in parallel while still understanding their relationship to other workspaces in the your task graph.
The name topo here is not a special name. It is short for "topological", so it helps indicate
why it exists, but you can call this task anything you want.
Why does this work?
We can more deeply understand why this works by taking a look at pipelines that almost fulfill our requirements.
You can achieve parallelism with your tasks by omitting dependsOn from your task definition like below:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"typecheck": {} // Uh oh, not quite!
}
}Your typecheck tasks will successfully run in parallel - but they won't know about their workspace dependencies!
We can demonstrate this using these steps:
- Run
turbo typecheck - Change some source code in your
uipackage - Run
turbo typecheck --filter=web
If you do this, you will hit cache in Step 3 - but you shouldn't! You could have created a type error in your web workspace that comes from the changes in the ui package's code. The cache hit in Step 3 would be incorrect, hiding the type error from you.
To solve this problem, you may choose to depend on your topological dependency graph directly, much like you would for a build task:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"typecheck": {
"dependsOn": ["^typecheck"] // Uh oh, not quite!
}
}
}Now you have the right cache behavior: web will miss cache when ui code changes. That's great - but we just lost the parallelism that was making our pipeline execute so fast. The typecheck task in the ui workspace now has to finish before the task in web starts.
What if we could depend on a task in ui that "finishes instantly," starting the typecheck commands in dependent workspaces much sooner?
This is where the "fake" topo task comes in:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"typecheck": {
"dependsOn": ["topo"]
}
}
}In this pipeline, we declare a "synthetic" task called topo. Since we don't
have a topo script in any of our package.json files, the turbo typecheck
pipeline will go straight to running all typecheck scripts in parallel, meeting our first requirement.
But this topo task also creates a "synthetic" workspace-task dependency from web to ui,
and from docs to ui. This means that when you change code in ui, you will also get
a cache miss for the workspaces in web and docs, meeting the second requirement.
The pipeline declares that typecheck depends on the topo task, and topo depends on ^topo. In
English, this means that the topo task of the same workspace must run before all typecheck tasks,
and the topo task of all package dependencies must run before the topo task itself.

Why doesn't typecheck directly depend on ^topo, you ask? Because we want our workspaces to recursively
wire up package dependencies via synthetic tasks. If typecheck depends on ^topo, turbo will stop
adding to the graph after the first level of dependencies.