---
id: "packages/typescript-config/extending"
title: "Extending"
description: "How extends combines the presets with your own tsconfig, and where files, paths, environments and frameworks are set."
url: "/packages/typescript-config/extending"
locale: "en"
revision: "9c6a0f324265c0f0"
---

# Extending

## How `extends` works here

`"extends": "@workspace/typescript-config/nodejs.json"` is resolved like a package import: from
the folder of your `tsconfig.json` upwards to `node_modules/@workspace/typescript-config`, which
pnpm links to your copy in `packages/typescript-config`. That is why the package is declared and
installed (see [Getting started](/packages/typescript-config/getting-started)). The package has no
`exports` map; the four file names are its whole surface.

TypeScript then reads three layers, each over the previous one:

1. `base.json`;
2. the preset you extend;
3. your own `tsconfig.json`.

Two rules decide the result:

- **A setting is replaced as a whole.** `compilerOptions` merge key by key, but an array such as
  `lib`, `types` or `plugins` is not merged: yours replaces the inherited one. To add a type
  package to `nodejs.json`, restate `node`:

  ```json
  {
    "extends": "@workspace/typescript-config/nodejs.json",
    "compilerOptions": {
      "types": ["node", "vitest/globals"]
    },
    "include": ["src"]
  }
  ```

  Without `node`, the result is `"types": ["vitest/globals"]` only, and `process` is unknown.
- **Paths are relative to the file that sets them.** `include`, `exclude`, `outDir`, `rootDir` and
  `paths` in your `tsconfig.json` are resolved from your folder. The presets set none of them.

## What you add

| Concern | Where | Example |
| --- | --- | --- |
| which files | `include`, `exclude` | `"include": ["src"]`, `"exclude": ["node_modules", "dist"]` |
| output | `noEmit`, or `outDir` and `rootDir` | `"noEmit": true`; or `"outDir": "dist", "rootDir": "src"` |
| import aliases | `paths` | `"paths": {"@/*": ["./*"]}` |
| environment | `types` | `"types": ["node"]` for Node globals outside `nodejs.json` |
| test globals | `types`, with what the preset already had | `"types": ["node", "vitest/globals"]` |
| framework | the preset, plus the framework's own files | `nextjs.json` with `next-env.d.ts` in `include` |
| editor plugins | `plugins`, restating the preset's | `"plugins": [{"name": "next"}]` |

These are the additions the shared standard allows (`docs/standards/CONFIG_STANDARDS.md` § 3 in
your copy of the standards): `include`, `exclude`, `outDir`, `rootDir`, `paths`, `noEmit`, `types`
and `plugins`.

Re-declaring `lib` is possible but almost never needed: the base already carries the shared set,
and a local copy drifts from it.

## What you do not change

Do not relax `strict`, `noUncheckedIndexedAccess`, `target`, `moduleResolution` or `declaration`.
They are the guarantees every package of the Active packages is written against. A repository that
must deviate records that as a decision in its own `docs/DECISIONS.md`, with its reason; a
deviation that would serve every repository is proposed to the maintainer of Foundation instead.

Do not edit the copy in `packages/typescript-config`. A preset change is made in Foundation, with
a changelog entry, and reaches every repository with the next copy.

## Packages that travel to other repositories

A package that is itself handed to other repositories (such as `active-format` and `active-ui`)
does **not** extend this configuration. A bundler in the receiving repository reads the
`tsconfig.json` of every source file it compiles, and a path to a preset that the receiver may not
have would break that. Such a package carries a self-contained `tsconfig.json` with the values of
the preset at the time it was written. Your own apps and packages extend the presets.
