JSON Config Files: package.json, tsconfig.json & More Explained

JSON configuration files are the backbone of modern development tooling — virtually every JavaScript, TypeScript, and cross-platform project relies on at least 5 JSON config files to define dependencies, compiler options, linting rules, and editor settings. The most common is package.json, which every npm, pnpm, and Yarn project requires. tsconfig.json controls TypeScript compilation for over 50 million weekly npm downloads of TypeScript. .eslintrc.json, .prettierrc, and .babelrc.json configure code quality tools. VS Code uses settings.json and launch.json for editor and debugger config. devcontainer.json defines containerized development environments used by GitHub Codespaces and VS Code Dev Containers. Unlike YAML or TOML alternatives, standard JSON config files cannot include comments — a common frustration solved by JSON5 format or JSONC (JSON with Comments) extensions used by tsconfig.json and VS Code. This guide covers the most important JSON config files, their key fields, and patterns for structuring and sharing configuration across projects.

Need to validate or pretty-print a JSON config file? Jsonic's formatter handles it instantly.

Open JSON Formatter

package.json: The Required Config

Every Node.js, npm, pnpm, and Yarn project requires exactly 1 file: package.json. It is the central manifest that defines the project name, version, entry points, scripts, and all dependencies. Running npm init -y generates a minimal package.json in under 1 second. The file follows strict JSON syntax rules — no comments, no trailing commas.

The core fields are name and version (required for published packages), main (CommonJS entry point), module (ESM entry point), and the exports map for dual CJS/ESM packages. The scripts object defines runnable commands: npm run build, npm test, npm start. The dependencies object lists runtime packages; devDependencies lists build-time and test packages. peerDependencies declares packages the host application must provide — common for plugins and UI libraries that must share a single instance with the consuming app.

{
  "name": "my-library",
  "version": "1.0.0",
  "description": "A minimal example package",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "test": "vitest",
    "lint": "eslint src"
  },
  "engines": { "node": ">=20" },
  "dependencies": {
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "tsup": "^8.0.0",
    "vitest": "^1.0.0"
  }
}

The exports map, introduced in Node.js 12, replaces main and module for dual CJS/ESM packages — modern bundlers and Node.js 20+ resolve it first. The engines field pins the required Node.js version, which npm enforces with a warning during install. The workspaces array (e.g., "workspaces": ["packages/*"]) enables monorepo support in npm, Yarn, and pnpm — linking local packages without publishing to a registry. Use Jsonic's formatter to validate your package.json against strict JSON syntax rules.

tsconfig.json: TypeScript Compiler Options

tsconfig.json is the configuration file for the TypeScript compiler. It controls how TypeScript transpiles and type-checks your code — every project with TypeScript installed needs one. The most important section is compilerOptions, which contains over 100 settings. For most projects, only 8 to 15 of them need to be set explicitly.

Key compilerOptions fields: target sets the JavaScript output version ("ES2022" for Node.js 20); module sets the module system ("NodeNext" for Node.js, "ESNext" for bundlers); strict enables 8 sub-checks including strictNullChecks and noImplicitAny; outDir sets the output directory; rootDir sets the source root; paths defines import aliases (e.g., "@/*": ["./src/*"]). The include and exclude arrays control which files TypeScript processes — include defaults to all .ts files if omitted.

{
  "extends": "@tsconfig/node20",
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

The extends field enables inheritance: "extends": "@tsconfig/node20" pulls in a community-maintained base config optimized for Node.js 20 — install it with npm install -D @tsconfig/node20. Child config values override the base. Strict mode alone enables 8 sub-checks, each of which can also be set individually. For JSON Schema-aware editing of tsconfig.json, VS Code automatically validates options and provides autocomplete because TypeScript ships a bundled JSON Schema for its config.

.eslintrc.json and the Flat Config Migration

ESLint v8 and earlier used .eslintrc.json as the primary config format. It supported 5 top-level keys: env (browser globals, Node.js globals), extends (shareable configs), rules (individual rule overrides), plugins (rule packages), and parser (custom parsers like @typescript-eslint/parser). ESLint v9, released in 2024, introduced flat config (eslint.config.js) as the new default. .eslintrc.json still works in ESLint v9 via compatibility mode but is deprecated and will be removed in ESLint v10.

A minimal .eslintrc.json for a TypeScript project with 3 custom rules:

{
  "env": {
    "browser": true,
    "es2022": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "rules": {
    "no-console": "warn",
    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
    "@typescript-eslint/explicit-function-return-type": "off"
  }
}

The flat config equivalent in eslint.config.js replaces extends with an array of config objects and uses explicit imports instead of string-based plugin names. For new projects started in 2025 or later, use flat config directly — it is the only format that will be supported in ESLint v10. For existing projects on ESLint v8, the .eslintrc.json format continues to work until the v10 migration. Use Zod validation to programmatically validate your ESLint config shape when building tooling scripts.

.prettierrc and .editorconfig

Prettier is the dominant code formatter for JavaScript and TypeScript projects, with over 40 million weekly npm downloads. It reads configuration from .prettierrc, .prettierrc.json, .prettierrc.js, or the prettier key in package.json — all are equivalent. Prettier's key options: printWidth (default 80 characters per line), tabWidth (default 2 spaces), semi (default true — add semicolons), singleQuote (default false — use double quotes), trailingComma (default "all" in Prettier v3 — trailing commas everywhere valid in ES5+).

// .prettierrc.json
{
  "printWidth": 100,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "bracketSpacing": true,
  "arrowParens": "always"
}

.editorconfig is an editor-agnostic format supported by over 30 editors and IDEs without plugins. It enforces 5 basic formatting properties: indent_style (space or tab), indent_size (number of spaces), end_of_line (lf, crlf, or cr), charset (utf-8), and trim_trailing_whitespace. Unlike Prettier which formats code structure, .editorconfig operates at the editor keystroke level — it inserts the correct indentation and line endings as you type.

# .editorconfig
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

Both files should be committed to version control. Use Jsonic's JSON formatter to validate your JSON config files and catch trailing commas or syntax errors before committing.

VS Code: settings.json and launch.json

VS Code uses 3 JSON config files under .vscode/: settings.json for editor behavior, launch.json for the debugger, and extensions.json for recommended extensions. All 3 support JSONC — you can add // comments freely. Workspace settings.json at .vscode/settings.json overrides user-scope settings for everyone working in the repository, making it the right place for team-wide formatting and language settings.

// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

launch.json configures the VS Code debugger for 1 or more launch configurations. Each configuration specifies a type (node, chrome, python), a request (launch or attach), a program entry point, and optional env overrides. For Node.js projects, the most common configuration launches ts-node or the compiled output directly.

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug TypeScript",
      "runtimeArgs": ["--loader", "ts-node/esm"],
      "program": "${workspaceFolder}/src/index.ts",
      "env": { "NODE_ENV": "development" },
      "sourceMaps": true,
      "console": "integratedTerminal"
    }
  ]
}

devcontainer.json at .devcontainer/devcontainer.json defines the containerized development environment used by GitHub Codespaces and VS Code Dev Containers — adopted by over 1 million GitHub Codespaces users. It specifies the container image, forwardPorts, and VS Code extensions to install automatically. Every team member who opens the repository in Codespaces gets an identical, pre-configured environment in under 2 minutes — no manual setup required. See the JSON syntax rules guide for the JSONC comment syntax that all these VS Code files support.

Key terms defined

package.json
The mandatory JSON manifest file for every Node.js project that declares the package name, version, entry points, runnable scripts, and all runtime and development dependencies consumed by npm, pnpm, and Yarn.
tsconfig.json
The JSON configuration file read by the TypeScript compiler that specifies compiler options (target JavaScript version, module format, strictness checks), file inclusion/exclusion patterns, and an optional base config to extend via the extends field.
JSONC (JSON with Comments)
A superset of JSON that allows // single-line and /* */ block comments, used by tsconfig.json, VS Code config files, and some other developer tools where human annotation of configuration is valuable — standard JSON parsers cannot parse JSONC files.
devDependencies
A package.json field listing packages required only during development and build time (TypeScript, bundlers, test frameworks) that are excluded when npm install --omit=dev is run in production environments to reduce install size.
flat config (eslint.config.js)
The new ESLint v9 configuration format that replaces .eslintrc.json, using a JavaScript module that exports an array of config objects with explicit imports instead of string-based plugin and extends references.
exports map
A package.json field introduced in Node.js 12 that maps package subpath imports to specific files, enabling dual CommonJS/ESM packages by returning different entry points depending on the import condition (require vs import).
devcontainer.json
A JSON configuration file at .devcontainer/devcontainer.json that defines a reproducible containerized development environment for VS Code Dev Containers and GitHub Codespaces, specifying the container image, port forwarding, and VS Code extensions to pre-install.

Frequently asked questions

Why can't I add comments to JSON config files?

Standard JSON (RFC 8259) forbids comments. The spec intentionally excluded them to keep the format simple and unambiguous for machine parsing. Tools like tsconfig.json and VS Code settings use JSONC (JSON with Comments), a superset that allows // single-line and /* */ block comments. For other JSON config files, workarounds include a $comment key (a string field ignored by most tools) or switching to YAML or TOML formats which natively support comments. npm's package.json does not support JSONC — you cannot add comments to package.json in strict JSON mode. If you need annotated config, consider using a separate docs file. Some bundlers like webpack accept JSONC in their config files. Always check whether a specific tool supports JSONC before adding comments to avoid parse errors. Use Jsonic's formatter to validate your JSON config and catch syntax issues immediately.

What is the difference between dependencies and devDependencies in package.json?

dependencies are required at runtime — your application needs them in production (Express, React, database drivers). devDependencies are only needed during development and build time: the TypeScript compiler, test frameworks like Jest or Vitest, bundlers like Vite or webpack, and linters. When a user installs your published npm package, only dependencies are installed automatically —devDependencies are skipped. When deploying a Node.js app, run npm install --omit=dev to skip devDependencies and reduce the installed node_modules size by 30 to 90 percent depending on the project. In a monorepo or CI/CD pipeline, omitting devDependencies in production Docker images significantly reduces image size and attack surface. peerDependencies declare packages the host application must provide — common for plugins and UI component libraries that must share a single instance with the consuming app.

How do I extend a base tsconfig.json?

Use the extends field: "extends": "@tsconfig/node20" to inherit from a published base config, or "extends": "./tsconfig.base.json" to inherit from a local file. The child config's values override the base config's values. compilerOptions fields are merged key by key — a child can override individual options like target or outDir without repeating the full compilerOptions object. The include and exclude arrays are replaced entirely in the child, not merged. The files array is also replaced, not merged. Multiple levels of inheritance are supported. Published base configs are available on npm under the @tsconfig scope — @tsconfig/node20, @tsconfig/strictest, and @tsconfig/next are common starting points that encode best-practice compiler option sets. See the JSON Schema guide for how TypeScript's own schema validates tsconfig.json options in editors.

What does 'strict' mode do in tsconfig.json?

"strict": true in tsconfig.json enables 8 compiler checks in a single flag: strictNullChecks (null and undefined are not assignable to other types), noImplicitAny (variables must have explicit types when they cannot be inferred), strictFunctionTypes (function parameter types are checked contravariantly), strictBindCallApply (bind, call, and apply are type-checked), strictPropertyInitialization (class properties must be initialized in the constructor), noImplicitThis (this expressions must have a known type), alwaysStrict (emits "use strict" in every output file), and strictBuiltinIteratorReturn (iterator return values are typed more precisely). Each sub-check can also be set individually to false to opt out while keeping the others. Most new projects should enable strict: true from the start — retrofitting strict mode onto an existing codebase typically requires fixing hundreds of type errors.

Should I commit .vscode/settings.json to git?

It depends on what the settings control. Workspace settings that enforce consistent formatting and tooling across the team — editor.formatOnSave, editor.defaultFormatter, typescript.tsdk, editor.codeActionsOnSave — benefit from being committed because they ensure every contributor gets the same behavior without manual setup. Personal preferences such as font size, color theme, minimap visibility, and keybindings should go in user settings, not workspace settings. A common pattern is to commit .vscode/settings.json with formatting and language settings, and add .vscode/extensions.json with a recommendations list so new contributors are prompted to install the required extensions. Use .gitignore selectively to exclude specific VS Code files if needed, and document the intent in the project README so contributors understand which settings are intentionally shared.

What is the difference between .prettierrc and prettier in package.json?

They are functionally equivalent — Prettier checks both locations and applies the same configuration. The prettier key in package.json keeps all project configuration centralized in a single file, which reduces the number of config files at the project root. A standalone .prettierrc.json is more explicit and easier for editors and contributors to discover; it also allows JSON Schema validation with $schema. Both approaches support the same options: printWidth, tabWidth, semi, singleQuote, trailingComma, bracketSpacing, and others. The --config CLI flag overrides both. If both exist simultaneously, Prettier uses .prettierrc and ignores the package.json key. For projects with many tools sharing package.json keys, a standalone .prettierrc.json reduces cognitive load by isolating formatting config. Validate either file with Jsonic's JSON formatter.

Ready to work with JSON config files?

Use Jsonic's JSON formatter to validate your package.json, tsconfig.json, or any other JSON config file. Catch syntax errors like trailing commas before they break your build.

Open JSON Formatter