Configuring Workspaces
Most monorepos can declare a turbo.json
in the root directory with a uniform
pipeline that apply to all workspaces. Sometimes, a monorepo can contain
workspaces that need to configure their tasks differently. To accommodate this,
starting in version 1.8, Turborepo enables you to extend the root configuration
with a turbo.json
in any workspace. This flexibility enables a more diverse
set of apps and packages to co-exist, and allows workspace owners to maintain
specialized tasks and configuration without affecting other apps and packages of
the monorepo.
How it Works
To override the configuration for any task defined in the root turbo.json
, add
a turbo.json
file in any workspace of your monorepo with a top-level extends
key:
{
"extends": ["//"],
"pipeline": {
"build": {
// custom configuration for the build task in this workspace
},
// new tasks only available in this workspace
"special-task": {},
}
}
For now, the only valid value for the extends
key is ["//"]
.
//
is a special name used to identify the root directory of the monorepo.
Configuration in a workspace can override any of the configurations for a
pipeline task. If you don't include a key, the configuration is inherited
from the extended turbo.json
.
Examples
To illustrate, let's look at some use cases.
Different Frameworks
Let's say your monorepo has multiple Next.js (opens in a new tab) apps, and one SvelteKit (opens in a new tab)
app. Both frameworks create their build output with a build
script in their
respective package.json
s. You could configure Turborepo to run these tasks
with a single turbo.json
at the root like this:
{
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**"],
}
}
}
Notice that both .next/**
and .svelte-kit/**
need to be specified as
outputs
, even though Next.js apps do not generate a .svelte-kit
directory, and
vice versa. With Workspace Configurations, you can instead add custom
configuration in the SvelteKit workspace in apps/my-svelte-kit-app/turbo.json
:
{
"extends": ["//"],
"pipeline": {
"build": {
"outputs": [".svelte-kit/**"]
}
}
}
and remove the config from the root configuration:
{
"pipeline": {
"build": {
- "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**"]
+ "outputs": [".next/**", "!.next/cache/**"]
}
}
}
This not only makes each configuration easier to read, it puts the configuration closer to where it is used.
Specialized Tasks
In another example, say that the build
task in one workspace dependsOn
a
compile
task. You could universally declare it as dependsOn: ["compile"]
.
This means that your root turbo.json
has to have an empty compile
task
entry:
{
"pipeline": {
"build": {
"dependsOn": ["compile"]
},
"compile": {}
}
}
With Workspace Configurations, you can move that compile
task into the
apps/my-custom-app/turbo.json
,
{
"extends": ["//"],
"pipeline": {
"build": {
"dependsOn": ["compile"]
},
"compile": {}
}
}
and remove it from the root:
{
"pipeline": {
+ "build": {}
- "build": {
- "dependsOn": ["compile"]
- },
- "compile": {}
}
}
Now, the owners of my-app
, can have full ownership over their build
task,
but continue to inherit any other tasks defined at the root.
Comparison to Workspace-specific tasks
At first glance, Workspace Configurations may sound a lot like the
workspace#task
syntax in the root turbo.json
. The features are
similar, but have one significant difference: when you declare a Workspace-specific
in the root turbo.json
, it completely overwrites the baseline task
configuration. With a Workspace Configuration, the task configuration is merged
instead.
Consider the example of the monorepo with multiple Next.js apps and a Sveltekit
app again. Without a Workspace-specific task, you might configure your root
turbo.json
like this:
{
"pipeline": {
"build": {
"outputMode": "hash-only",
"inputs": ["src/**"],
"outputs": [".next/**", "!.next/cache/**"],
},
"my-sveltekit-app#build": {
"outputMode": "hash-only", // must duplicate this
"inputs": ["src/**"], // must duplicate this
"outputs": [".svelte-kit/**"]
}
}
}
In this example, my-sveltekit-app#build
completely overwrites build
for the
Sveltekit app, so outputMode
and inputs
also need to be duplicated.
With Workspace Configurations, outputMode
and inputs
are inherited, so
you don't need to duplicate them. You only need to override outputs
my-sveltekit-app
config.
Although there are no plans to remove Workspace-specific task configurations, we expect that Workspace Configurations can be used for most use cases instead.
Limitations
Although the general idea is the same as the root turbo.json
, Workspace
Configurations come with a set of guardrails that can prevent workspaces from creating
confusing situations. These guardrails are listed here to make it clear that
they are intentional, rather than accidental:
-
Workspace Configurations cannot use the
workspace#task
syntax as pipeline entriesThe
workspace
is inferred based on the location of the config, and it is not possible to change configuration for another workspace. For example, in a Workspace Configuration for 'my-nextjs-app':apps/my-nextjs-app/turbo.json{ "pipeline": { "my-nextjs-app#build": { // ❌ This is not allowed. Even though it's // referencing the correct workspace, "my-nextjs-app" // is inferred, and we don't need to specify it again. // This syntax also has different behavior, so we do not want to allow it. // (see "Comparison to Workspace-specific tasks" section) }, "my-sveltekit-app#build": { // ❌ Changing configuration for the "my-sveltekit-app" workspace // from Workspace Configuraton in "my-nextjs-app" is not allowed. }, "build": { // ✅ just use the task name! }, } }
Note that the
build
task can still depend on a Workspace-specific task:apps/my-nextjs-app/turbo.json{ "pipeline": { "build": { // ✅ It's still ok to have workspace#task in dependsOn! "dependsOn": ["some-pkg#compile"] }, } }
-
Workspace Configurations cannot override anything outside the
pipeline
key.For example, it is not possible to override
globalEnv
orglobalDependencies
. We expect that monorepo owners should control this absolutely, and if this config is not truly global, it should not be configured that way. -
Root turbo.json cannot use the
extends
key.To avoid creating circular dependencies on workspaces, the root
turbo.json
cannot extend from anything.
If you have a use case for any of these, please file an issue (opens in a new tab)!
Troubleshooting
In large monorepos, it can sometimes be difficult to understand how Turborepo is
interpreting your configuration. To help, we've added a resolvedTaskDefinition
to the Dry Run output. If you run turbo run build --dry-run
, for example, the
output will include the combination of all turbo.json
configurations that were
considered before running the build
task.