Package Configurations

Package Configurations let a workspace keep a shared root task model while allowing individual packages to adjust task behavior where their build, development, or deployment needs differ. In a typical Turborepo workspace, the root configuration describes common tasks once, and those descriptions apply across the repository. That default is intentionally simple, but it can become too rigid when an application needs framework-specific outputs, a library needs a different build cache policy, or a package owner wants to add a task that should not affect the rest of the monorepo. Package-level configuration solves that tension by letting a package add its own configuration file that extends the root and then overrides or augments selected tasks.

Sources: apps/docs/content/docs/reference/package-configurations.mdx

Purpose and Scope

Use this reference when the question is not how to define tasks in general, but how to specialize them for one package without rewriting the whole workspace configuration. The key idea is inheritance. The root configuration remains the shared baseline, while a package configuration is a local layer that starts from that baseline and changes only what the package needs. This is especially useful in mixed workspaces where applications and libraries coexist. An application package may have deployable artifacts, long-running development servers, and framework caches, while a library package may have simpler compilation outputs and type declarations. Keeping those differences near the package makes ownership clearer and reduces accidental changes to unrelated packages.

Sources: apps/docs/content/docs/reference/package-configurations.mdx, apps/docs/content/docs/core-concepts/package-types.mdx

Package Configurations also fit into Turborepo’s graph model. Turborepo understands the workspace through a package graph created from package manager relationships, then uses task definitions to build a task graph. Package-level configuration changes the definition of a task for one package, but it does not replace the need for the workspace to be discoverable through package manager configuration and package metadata. This means a local override is still part of the larger scheduling story: dependencies between packages determine which tasks can run first, while the merged task configuration determines what each task means when it is selected for execution.

Sources: apps/docs/content/docs/core-concepts/package-and-task-graph.mdx, apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx

Relevant Source Files

  • apps/docs/content/docs/reference/package-configurations.mdx - Defines the Package Configurations feature, including package-level turbo.json files, inheritance behavior, array replacement, the extension microsyntax, and extending from other packages.
  • apps/docs/content/docs/core-concepts/package-and-task-graph.mdx - Explains the package graph and task graph concepts that package-level task configuration participates in at execution time.
  • apps/docs/content/docs/core-concepts/package-types.mdx - Defines application packages and library packages, which helps explain why different packages often need different task behavior.
  • apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx - Shows how internal packages become discoverable through package metadata and how package relationships form the workspace model.
  • apps/docs/content/docs/guides/single-package-workspaces.mdx - Clarifies how Turborepo behaves in a single-package workspace, where package-scoped task patterns are not meaningful.
  • apps/docs/content/docs/messages/package-task-in-single-package-workspace.mdx - Provides the troubleshooting rule that package-named task declarations are not permitted in single-package mode.

How Package Configuration Works

Create a package configuration by placing a configuration file inside a package and adding a top-level extension list. The first extension must identify the root of the monorepo. That special root reference makes the package configuration an extension of the shared workspace baseline rather than an isolated configuration. Once that relationship is declared, the package can override a task already defined at the root or add a new task that only exists for that package. This is the central pattern: declare broadly shared behavior once, then use package-local configuration only for the packages that genuinely diverge. That approach keeps the common path easy to reason about while still supporting specialized applications and packages.

Sources: apps/docs/content/docs/reference/package-configurations.mdx

./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": [".next/**"]
    },
    "special-task": {}
  }
}

The extension list can also include other packages after the root. In that pattern, the package uses the package name from another package’s package metadata to compose shared task configuration. This enables a workspace to factor reusable task policy into a package and let related packages inherit it, instead of duplicating framework-specific settings in every application. For example, several applications with the same framework may share a build output policy and a persistent development task policy. The root still anchors the inheritance chain, and the additional package reference supplies another layer of shared behavior for packages that opt into it.

Sources: apps/docs/content/docs/reference/package-configurations.mdx, apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx

Inheritance and Override Rules

The most important reference detail is that inherited properties do not all merge the same way. Scalar properties are inherited. A scalar property is a single value rather than a list, so package owners only need to repeat it when they want to change it. The documented scalar examples include log output behavior, cache enablement, persistent execution, and interactivity. If the root says that a task should use a particular log mode, each package receives that setting unless its package configuration says otherwise. This makes root configuration a good place for broad policy choices that should stay consistent across most packages.

Sources: apps/docs/content/docs/reference/package-configurations.mdx

Array properties replace by default. This is deliberately different from scalar inheritance because arrays often describe concrete task inputs, outputs, dependency relationships, or environment variables. When a package supplies a new array for a task, that array becomes the package’s value for the field; the root array is not automatically appended. This matters for cache correctness. If the root build task records a generic distribution directory but a framework application emits a different artifact directory, the package should be able to replace the output list precisely. Readers should treat array replacement as a safety rule: when a list is present locally, it is the complete list unless the extension marker is used.

Sources: apps/docs/content/docs/reference/package-configurations.mdx

./turbo.json
{
  "tasks": {
    "build": {
      "outputs": ["dist/**"],
      "env": ["NODE_ENV"]
    }
  }
}
./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": [".next/**"]
    }
  }
}

When a package needs to preserve the inherited array and add to it, use the extension marker as the first array element. The marker is a small microsyntax that says the inherited array should be expanded at that position before the package-specific values are added. The source reference calls out that it works with output, environment, input, dependency, pass-through environment, and companion task arrays. The first-position requirement is important because it makes the merge unambiguous and easy to inspect. If a package author forgets the marker, the local array replaces the inherited array, which can unintentionally drop root inputs, dependency edges, or environment declarations.

Sources: apps/docs/content/docs/reference/package-configurations.mdx

./apps/my-app/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": ["$TURBO_EXTENDS$", ".next/**"]
    }
  }
}

System-to-Code Mapping

At a system level, a Package Configuration is not just a file override; it is a way to describe a package-specific node in the task graph. The package graph supplies the package relationships, usually because internal packages are installed into applications or other libraries. The task graph then uses task dependencies to decide execution order. If a package changes its local build outputs or task dependencies, it changes how that task is interpreted for that package when the graph is constructed and executed. The package’s relationship to other packages still comes from workspace dependencies, so configuration and package metadata work together rather than replacing one another.

Sources: apps/docs/content/docs/core-concepts/package-and-task-graph.mdx, apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx

The distinction between application packages and library packages is a practical reason for package-specific configuration. Application packages are deployable endpoints of the workspace and commonly live in an applications directory. Library packages provide shared code and support applications rather than being independently deployed. Because applications often have framework-generated directories, development servers, and deployment-oriented build artifacts, they frequently need task settings that differ from libraries. Libraries, meanwhile, may care about compiled outputs, declaration files, or watch-mode compilation. Package Configurations give each package type enough local control without breaking the shared scheduling model that makes the workspace fast.

Sources: apps/docs/content/docs/core-concepts/package-types.mdx, apps/docs/content/docs/reference/package-configurations.mdx

Single-Package Workspace Considerations

Package Configurations are mainly a multi-package workspace feature. Turborepo can still provide caching and task parallelization in a single-package workspace, but features that depend on multiple packages do not make sense there. The single-package guide specifically notes that package tasks are not available in that mode, and the troubleshooting message explains that a task declared with a package name is not permitted when there is only one package. In that situation, define the task by its script name rather than using a package-qualified task declaration. If the repository should contain multiple packages, the fix is to make the workspace structure explicit so Turborepo can discover those packages.

Sources: apps/docs/content/docs/guides/single-package-workspaces.mdx, apps/docs/content/docs/messages/package-task-in-single-package-workspace.mdx

./turbo.json
{
  "tasks": {
    "build": {
      "cache": true
    }
  }
}

This distinction helps avoid a common modeling mistake. A single application can use Turborepo to run setup tasks, development tasks, linting, type checking, formatting, and builds, but it does not need package-level inheritance because there are no sibling packages to specialize. A multi-package repository, by contrast, may have shared root definitions plus local overrides in selected packages. When deciding between these patterns, first ask whether there is more than one package in the workspace and whether the package manager can identify them. Then decide whether differences belong in local package configuration or in a shared root task that every package should inherit.

Sources: apps/docs/content/docs/guides/single-package-workspaces.mdx, apps/docs/content/docs/reference/package-configurations.mdx

Reference Summary

ConceptBehaviorWhen to use it
Root extensionA package configuration starts from the root configuration using the required root entry in its extension list.Use for every package-level turbo.json that extends workspace defaults.
Scalar fieldsSingle-value task properties are inherited unless explicitly overridden.Use root defaults for shared cache, log, persistence, and interactivity policy.
Array fieldsList-valued task properties replace the inherited list by default.Use when a package needs a complete package-specific list of outputs, inputs, dependencies, or environment values.
Extension markerThe marker at the beginning of an array preserves inherited values and appends local values.Use when package-specific settings should add to, not replace, the root list.
Package extensionA package can extend another package’s configuration by package name after the root entry.Use for shared framework or package-family task policy.

Practical Guidance and Next Steps

Prefer a small number of shared root tasks and package overrides that are easy for package owners to justify. If many packages repeat the same local configuration, promote that behavior to the root or to a shared package configuration that related packages can extend. If only one application needs special build artifacts, keep that override beside the application. Review array fields carefully during code review because replacement versus extension has direct effects on caching, dependency ordering, and runtime environment behavior. For broader task design, read the configuration reference and task graph concepts next; for repository shape, pair this page with the structuring and internal package guidance.

Sources: apps/docs/content/docs/reference/package-configurations.mdx, apps/docs/content/docs/core-concepts/package-and-task-graph.mdx, apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx