Configuring tasks

Turborepo will always run tasks in the order described in your turbo.json configuration and Package Graph, parallelizing work whenever possible to ensure everything runs as fast as possible. This is faster than running tasks one at a time, and it's a part of what makes Turborepo so fast.

For example, yarn workspaces run lint && yarn workspaces run test && yarn workspaces run build would look like this:

A graphical representation of `turbo run lint test build`. It shows all tasks running in parallel, with much less empty space where scripts are not being ran.

But, to get the same work done faster with Turborepo, you can use turbo run lint test build:

A graphical representation of `turbo run lint test build`. It shows all tasks running in parallel, with much less empty space where scripts are not being ran.

Getting started

The root turbo.json file is where you'll register the tasks that Turborepo will run. Once you have your tasks defined, you'll be able to run one or more tasks using turbo run.

  • If you're starting fresh, we recommend creating a new repository using create-turbo and editing the turbo.json file to try out the snippets in this guide.
  • If you're adopting Turborepo in an existing repository, create a turbo.json file in the root of your repository. You'll be using it to learn about the rest of the configuration options in this guide.
turbo.json
package.json

Defining tasks

Each key in the tasks object is a task that can be executed by turbo run. Turborepo will search your packages for scripts in their package.json that have the same name as the task.

To define a task, use the tasks object in turbo.json. For example, a basic task with no dependencies and no outputs named build might look like this:

./turbo.json
{
  "tasks": {
    "build": {} // Incorrect!
  }
}

If you run turbo run build at this point, Turborepo will run all build scripts in your packages in parallel and won't cache any file outputs. This will quickly lead to errors. You're missing a few important pieces to make this work how you'd expect.

Running tasks in the right order

The dependsOn key is used to specify the tasks that must complete before a different task begins running. For example, in most cases, you want the build script for your libraries to complete before your application's build script runs. To do this, you'd use the following turbo.json:

./turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"] 
    }
  }
}

You now have the build order you would expect, building dependencies before dependents.

But be careful. At this point, you haven't marked the build outputs for caching. To do so, jump to the Specifying outputs section.

Depending on tasks in dependencies with ^

The ^ microsyntax tells Turborepo to run the task starting at the bottom of the dependency graph. If your application depends on a library named ui and the library has a build task, the build script in ui will run first. Once it has successfully completed, the build task in your application will run.

This is an important pattern as it ensures that your application's build task will have all of the necessary dependencies that it needs to compile. This concept also applies as your dependency graph grows to a more complex structure with many levels of task dependencies.

Depending on tasks in the same package

Sometimes, you may need to ensure that two tasks in the same package run in a specific order. For example, you may need to run a build task in your library before running a test task in the same library. To do this, specify the script in the dependsOn key as a plain string (without the ^).

./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["build"] 
    }
  }
}

Depending on a specific task in a specific package

You can also specify an individual task in a specific package to depend on. In the example below, the build task in utils must be ran before any lint tasks.

./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

You can also be more specific about the dependent task, limiting it to a certain package:

./turbo.json
{
  "tasks": {
    "web#lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

With this configuration, the lint task in your web package can only be ran after the build task in the utils package is complete.

No dependencies

Some tasks may not have any dependencies. For example, a task for finding typos in Markdown files likely doesn't need to care about the status of your other tasks. In this case, you can omit the dependsOn key or provide an empty array.

./turbo.json
{
  "tasks": {
    "spell-check": {
      "dependsOn": [] 
    }
  }
}

Specifying outputs

Turborepo caches the outputs of your tasks so that you never do the same work twice. We'll discuss this in depth in the Caching guide, but let's make sure your tasks are properly configured first.

The outputs key tells Turborepo files and directories it should cache when the task has successfully completed. Without this key defined, Turborepo will not cache any files. Hitting cache on subsequent runs will not restore any file outputs.

Below are a few examples of outputs for common tools:

./turbo.json
{
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"] 
    }
  }
}

For more on building globbing patterns for the outputs key, see the globbing specification.

Specifying inputs

The inputs key is used to specify the files that you want to include in the task's hash for caching. By default, Turborepo will include all files in the package that are tracked by Git. However, you can be more specific about which files are included in the hash using the inputs key.

As an example, a task for finding typos in Markdown files could be defined like this:

./turbo.json
{
  "tasks": {
    "spell-check": {
      "inputs": ["**/*.md", "**/*.mdx"] 
    }
  }
}

Now, only changes in Markdown files will cause the spell-check task to miss cache.

This feature opts out of all of Turborepo's default inputs behavior, including following along with changes tracked by source control. This means that your .gitignore file will no longer be respected, and you will need to ensure that you do not capture those files with your globs.

To restore the default behavior, use the $TURBO_DEFAULT$ microsyntax.

Restoring defaults with $TURBO_DEFAULT$

The default inputs behavior is often what you will want for your tasks. However, you can increase your cache hit ratios for certain tasks by fine-tuning your inputs to ignore changes to files that are known to not affect the task's output.

For this reason, you can use the $TURBO_DEFAULT$ microsyntax to fine-tune the default inputs behavior:

./turbo.json
{
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", "!README.md"] 
    }
  }
}

In this task definition, Turborepo will use the default inputs behavior for the build task, but will ignore changes to the README.md file. If the README.md file is changed, the task will still hit cache.

Registering Root Tasks

You can also run scripts in the package.json in the Workspace root using turbo. For example, you may want to run a lint task for the files in your Workspace's root directory in addition to the lint task in each package:

./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["^lint"]
    },
    "//#lint:root": {} 
  }
}

When to use Root Tasks

  • Linting and formatting of the Workspace root: You might have code in your Workspace root that you want to lint and format. For example, you might want to run ESLint or Prettier in your root directory.
  • Incremental migration: While you're migrating to Turborepo, you might have an in-between step where you have some scripts that you haven't moved to packages yet. In this case, you can create a Root Task to start migrating and fan the tasks out to packages later.
  • Scripts without a package scope: You may have some scripts that don't make sense in the context of specific packages. Those scripts can be registered as Root Tasks so you can still run them with turbo for caching, parallelization, and workflow purposes.

Advanced use cases

Using Package Configurations

Package Configurations are turbo.json files that are placed directly into a package. This allows a package to define specific behavior for its own tasks without affecting the rest of the repository.

In large monorepos with many teams, this allows teams greater control over their own tasks. To learn more, visit the Package Configurations documentation

Performing side-effects

Some tasks should always be ran no matter what, like a deployment script after a cached build. For these tasks, add "cache": false to your task definition.

./turbo.json
{
  "tasks": {
    "deploy": {
      "dependsOn": ["^build"],
      "cache": false
    },
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

Dependent tasks that can be ran in parallel

Some tasks can be ran in parallel despite being dependent on other packages. An example of tasks that fit this description are linters, since a linter doesn't need to wait for outputs in dependencies to run successfully.

Because of this, you may be tempted to define your check-types task like this:

./turbo.json
{
  "tasks": {
    "check-types": {} // Incorrect!
  }
}

This runs your tasks in parallel - but doesn't account for source code changes in dependencies. This means you can:

  1. Make a breaking change to the interface of your ui package.
  2. Run turbo check-types, hitting cache in an application package that depends on ui.

This is incorrect, since the application package will show a successful cache hit, despite not being updated to use the new interface. Checking for TypeScript errors in your application package manually in your editor is likely to reveal errors.

Because of this, you make a small change to your check-types task definition:

./turbo.json
{
  "tasks": {
    "check-types": {
      "dependsOn": ["^check-types"] // This works...but could be faster!
    }
  }
}

If you test out making breaking changes in your ui package again, you'll notice that the caching behavior is now correct. However, tasks are no longer running in parallel.

To meet both requirements (correctness and parallelism), you can introduce Transit Nodes to your Task Graph:

./turbo.json
{
  "tasks": {
    "transit": {
      "dependsOn": ["^transit"]
    },
    "check-types": {
      "dependsOn": ["transit"]
  },
}

These Transit Nodes create a relationship between your package dependencies using a task that doesn't do anything because it doesn't match a script in any package.jsons. Because of this, your tasks can run in parallel and be aware of changes to their internal dependencies.

In this example, we used the name transit - but you can name the task anything that isn't already a script in your Workspace.

Next steps

There are more options available in the Configuring turbo.json documentation that you will explore in the coming guides. For now, you can start running a few tasks to see how the basics work.

hours

Total Compute Saved
Get started with
Remote Caching →

On this page