Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

VS Code Configuration for TypeScript Development

For .NET engineers who know: Visual Studio 2022 — the IDE with integrated compiler, debugger, NuGet, test runner, designer, and everything else You’ll learn: The mental model shift from “full IDE” to “editor with extensions,” and how to configure VS Code to match Visual Studio’s productivity for TypeScript development Time: 10-15 min read

The .NET Way (What You Already Know)

Visual Studio is a full IDE. It has a built-in C# compiler (Roslyn), a code formatter, a debugger with process attach, a test runner with visual results, a NuGet UI, a project designer, and built-in Git integration. When you install Visual Studio, you get a coherent, co-designed set of tools maintained by Microsoft. The behavior of IntelliSense, the debugger, and the test runner are part of the product.

Extensions in Visual Studio exist but are secondary. The core experience works out of the box.

VS Code is not this. VS Code is a text editor with a powerful extension API. Without extensions, it is a fast, cross-platform text editor with good syntax highlighting and multi-cursor editing. With the right extensions, it becomes a productive TypeScript development environment. The TypeScript language server, the ESLint integration, the debugger, the test runner — each of these is an extension that must be installed and configured.

This distinction matters because: the configuration is your responsibility, it should be committed to the repository so the team shares it, and the experience is composable rather than fixed.

The VS Code Way

Essential Extensions

Install these extensions. The extension IDs are the exact identifiers to use with the Extensions panel or code --install-extension.

Core TypeScript Development

ExtensionID.NET Equivalent
ESLintdbaeumer.vscode-eslintRoslyn analyzer red squiggles
Prettier - Code Formatteresbenp.prettier-vscodeVisual Studio format-on-save
TypeScript (built-in)(built-in)Roslyn IntelliSense
Error Lensusernamehw.errorlensInline error display in Visual Studio
Path IntelliSensechristian-kohler.path-intellisenseFile path autocomplete in string literals

Git and Code Review

ExtensionID.NET Equivalent
GitLenseamodio.gitlensTeam Explorer git history + line-level blame
GitHub Pull Requestsgithub.vscode-pull-request-githubTeam Explorer PR integration

AI Assistance

ExtensionID.NET Equivalent
GitHub Copilotgithub.copilotGitHub Copilot for Visual Studio
GitHub Copilot Chatgithub.copilot-chatCopilot Chat panel

Framework-Specific

ExtensionIDWhen to Install
ES7+ React/Redux Snippetsdsznajder.es7-react-js-snippetsReact projects
Vue - Officialvue.volarVue 3 projects
Tailwind CSS IntelliSensebradlc.vscode-tailwindcssProjects using Tailwind
Prismaprisma.prismaAny project using Prisma

Quality of Life

ExtensionIDWhat It Does
Auto Rename Tagformulahendry.auto-rename-tagRenames matching HTML/JSX tag
Bracket Pair Colorizer(built-in since VS Code 1.60)Color-matched brackets
Todo Treegruntfuturist.todo-treeLists TODO comments across project

Installing Extensions from the CLI

# Install all team-standard extensions at once
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension usernamehw.errorlens
code --install-extension eamodio.gitlens
code --install-extension github.copilot
code --install-extension github.copilot-chat
code --install-extension github.vscode-pull-request-github
code --install-extension christian-kohler.path-intellisense
code --install-extension prisma.prisma
code --install-extension dsznajder.es7-react-js-snippets

Or open the Extensions panel (Ctrl+Shift+X / Cmd+Shift+X), search by name, and install.

Workspace Settings — .vscode/settings.json

VS Code settings exist at three levels: user (global), workspace (.vscode/settings.json), and folder (multi-root). Workspace settings are committed to the repository and override user settings for that project. This is how the team shares configuration.

Commit the following .vscode/settings.json to every TypeScript project:

{
  // ---- Formatting ----
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.rulers": [100],
  "editor.wordWrap": "off",

  // ---- TypeScript-specific formatting ----
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // ---- JSON formatting ----
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // ---- ESLint ----
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },

  // ---- TypeScript ----
  "typescript.preferences.importModuleSpecifier": "relative",
  "typescript.updateImportsOnFileMove.enabled": "always",
  "typescript.suggest.autoImports": true,
  "typescript.inlayHints.parameterNames.enabled": "literals",
  "typescript.inlayHints.variableTypes.enabled": false,

  // ---- Explorer ----
  "files.exclude": {
    "**/node_modules": true,
    "**/.git": true,
    "**/dist": true,
    "**/.next": true
  },
  "search.exclude": {
    "**/node_modules": true,
    "**/dist": true,
    "**/.next": true,
    "**/pnpm-lock.yaml": true
  },

  // ---- Editor Experience ----
  "editor.minimap.enabled": false,
  "editor.bracketPairColorization.enabled": true,
  "editor.guides.bracketPairs": "active",
  "editor.linkedEditing": true,
  "editor.suggest.preview": true
}

What each setting does, mapped to .NET equivalents:

SettingVS Code EffectVisual Studio Equivalent
editor.formatOnSaveRuns Prettier on every saveFormat document on save (Tools → Options)
editor.codeActionsOnSave with ESLintRuns ESLint auto-fixes on every saveQuick Fix on save (not a VS feature, but similar)
typescript.updateImportsOnFileMoveUpdates imports when you rename/move a fileVisual Studio does this for C# automatically
editor.linkedEditingRenames matching HTML/JSX tag when you rename oneNot a Visual Studio feature
search.excludeHides node_modules from search resultsSolution Explorer doesn’t show external packages

The extensions.json file prompts every developer who opens the repository to install the recommended extensions. This eliminates “it works on my machine” due to missing extensions.

{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "usernamehw.errorlens",
    "eamodio.gitlens",
    "github.copilot",
    "github.copilot-chat",
    "christian-kohler.path-intellisense",
    "prisma.prisma"
  ],
  "unwantedRecommendations": [
    "hookyqr.beautify",
    "ms-vscode.vscode-typescript-tslint-plugin"
  ]
}

unwantedRecommendations suppresses the prompt for extensions that conflict with your setup. beautify conflicts with Prettier. The old tslint plugin is superseded by @typescript-eslint.

When a developer opens the project, VS Code shows a notification: “Do you want to install the recommended extensions?” One click installs everything in the recommendations array.

Debugging Configuration — .vscode/launch.json

The debugger in VS Code is configured via launch.json. This is the equivalent of Visual Studio’s debug profiles (the dropdown next to the Run button).

For a NestJS API:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug NestJS",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/src/main.ts",
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
      "sourceMaps": true,
      "cwd": "${workspaceFolder}",
      "envFile": "${workspaceFolder}/.env",
      "console": "integratedTerminal",
      "restart": true
    },
    {
      "name": "Attach to Running Process",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "sourceMaps": true,
      "restart": true
    }
  ]
}

For a Next.js application:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Next.js (Server)",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/next",
      "args": ["dev"],
      "cwd": "${workspaceFolder}",
      "envFile": "${workspaceFolder}/.env.local",
      "sourceMaps": true,
      "console": "integratedTerminal"
    },
    {
      "name": "Debug Next.js (Client — Chrome)",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}",
      "sourceMaps": true
    }
  ]
}

To debug:

  1. Set a breakpoint by clicking in the left gutter (same as Visual Studio)
  2. Press F5 or click the Run and Debug panel (Ctrl+Shift+D)
  3. Select the configuration and press the play button

The experience is close to Visual Studio but requires this upfront configuration. Once launch.json is committed, every team member gets the same debug configurations automatically.

Attaching to a running process works similarly to Visual Studio’s “Attach to Process” (Ctrl+Alt+P):

# Start the dev server with the inspector enabled
node --inspect src/main.js

# Or for pnpm scripts:
NODE_OPTIONS='--inspect' pnpm dev

Then use the “Attach to Running Process” configuration in launch.json.

Task Configuration — .vscode/tasks.json

Tasks in VS Code are runnable actions bound to keyboard shortcuts or the Command Palette. They are equivalent to Visual Studio’s external tools or custom build events.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "pnpm: dev",
      "type": "shell",
      "command": "pnpm dev",
      "group": "build",
      "isBackground": true,
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      }
    },
    {
      "label": "pnpm: test",
      "type": "shell",
      "command": "pnpm test",
      "group": {
        "kind": "test",
        "isDefault": true
      },
      "presentation": {
        "reveal": "always",
        "panel": "shared"
      }
    },
    {
      "label": "pnpm: lint",
      "type": "shell",
      "command": "pnpm lint",
      "group": "build",
      "presentation": {
        "reveal": "always",
        "panel": "shared"
      }
    },
    {
      "label": "Prisma: migrate dev",
      "type": "shell",
      "command": "pnpm prisma migrate dev",
      "group": "none",
      "presentation": {
        "reveal": "always",
        "panel": "shared"
      }
    },
    {
      "label": "Prisma: studio",
      "type": "shell",
      "command": "pnpm prisma studio",
      "group": "none",
      "isBackground": true,
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      }
    }
  ]
}

Run tasks via:

  • Ctrl+Shift+P → “Tasks: Run Task” → select the task
  • Ctrl+Shift+B runs the default build task
  • Ctrl+Shift+T (not the default, but bindable) for the default test task

Multi-Root Workspaces (Monorepos)

If your project is a monorepo with a frontend and backend in separate directories, VS Code supports multi-root workspaces — a single VS Code window with multiple root folders, each with its own settings.

Create a .code-workspace file at the monorepo root:

{
  "folders": [
    {
      "name": "API (NestJS)",
      "path": "./apps/api"
    },
    {
      "name": "Web (Next.js)",
      "path": "./apps/web"
    },
    {
      "name": "Shared Packages",
      "path": "./packages"
    }
  ],
  "settings": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "extensions": {
    "recommendations": [
      "dbaeumer.vscode-eslint",
      "esbenp.prettier-vscode",
      "prisma.prisma"
    ]
  }
}

Open with: code my-monorepo.code-workspace

Each folder in the workspace can have its own .vscode/settings.json for folder-specific settings (e.g., the API folder might have NestJS-specific snippets enabled, the web folder might have React snippets). The workspace-level settings apply to all folders.

This is the equivalent of opening a Visual Studio Solution that contains multiple projects.

Keyboard Shortcuts

VS Code keyboard shortcuts will feel familiar if you have used Visual Studio, but some differ. The most important for TypeScript development:

ActionVS Code (Mac)VS Code (Win/Linux)Visual Studio Equivalent
Open file by nameCmd+PCtrl+PCtrl+, (Go to All)
Go to symbol in fileCmd+Shift+OCtrl+Shift+OCtrl+F12
Go to symbol in projectCmd+TCtrl+TSolution-wide search
Go to definitionF12F12F12
Peek definitionAlt+F12Alt+F12Alt+F12
Find all referencesShift+F12Shift+F12Shift+F12
Rename symbolF2F2F2
Open Command PaletteCmd+Shift+PCtrl+Shift+PCtrl+Q (Quick Launch)
Toggle terminalCtrl+`Ctrl+`Alt+F2 (terminal pane)
Format documentShift+Alt+FShift+Alt+FCtrl+K, Ctrl+D
Open ExtensionsCmd+Shift+XCtrl+Shift+XExtensions manager
Split editorCmd+\Ctrl+\Drag tab to split
Multi-cursorAlt+ClickAlt+ClickAlt+Click
Select all occurrencesCmd+Shift+LCtrl+Shift+LCtrl+Shift+H (replace)

Most valuable shortcut for TypeScript development: Cmd+P (Go to File). Type any part of a filename and navigate there instantly. This replaces browsing Solution Explorer.

Settings Sync

VS Code’s built-in Settings Sync (Cmd+Shift+P → “Settings Sync: Turn On”) syncs your personal settings, extensions, and keybindings across machines via your GitHub or Microsoft account. This covers your global user settings — the team workspace settings come from the repository.

Configure what to sync:

Settings Sync → Settings → What to Sync:
✓ Settings
✓ Keybindings
✓ Extensions
✓ UI State
✗ Profiles (can cause conflicts in team settings)

Team settings go in .vscode/settings.json (committed). Personal preferences go in user settings (synced). Workspace settings always win over user settings for that project.

Key Differences

ConcernVisual StudioVS Code
TypeScript/C# supportBuilt-in, always presentTypeScript language server (built-in), must configure
FormatterIDE-level settingPrettier extension + .prettierrc
LinterRoslyn analyzers (built-in)ESLint extension + eslint.config.mjs
DebuggerVisual Studio debugger (full featured)Node.js debugger via extension (powerful but requires config)
Test runnerTest Explorer (visual, built-in)Vitest extension or terminal
NuGet / packagesGUI + PM consoleTerminal only (pnpm add)
Team settings sharing.editorconfig (partial).vscode/settings.json (comprehensive)
Extension modelIDE plugins (heavier)Extensions (lighter, more composable)
Startup speedSlow (full IDE)Fast (text editor core)
Memory usageHigh (200MB-2GB typical)Lower (100-500MB typical)

Gotchas for .NET Engineers

Gotcha 1: Format on Save Requires the Right Default Formatter

After installing Prettier, editor.formatOnSave: true alone is not sufficient. VS Code must know which extension to use as the formatter. Without "editor.defaultFormatter": "esbenp.prettier-vscode" (or the per-language equivalent), VS Code may use the built-in TypeScript formatter instead of Prettier.

Symptoms: formatting does not match your .prettierrc config, or VS Code asks “Select a formatter” every time you save.

The fix is in .vscode/settings.json:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

The language-specific override ([typescript]) takes precedence over the global one. Set both to be safe.

Gotcha 2: ESLint Type-Checked Rules Require tsconfig.json in the Right Place

Some @typescript-eslint rules (including no-floating-promises) require the TypeScript compiler to provide type information. The ESLint extension needs to find your tsconfig.json. If your project has an unusual structure (tsconfig not at the workspace root, or a monorepo with multiple tsconfigs), the ESLint extension may silently skip type-checked rules.

Diagnosis: open the ESLint output panel (View → Output → ESLint) and look for errors referencing tsconfig.json or parserOptions.project.

Fix for monorepos — in each sub-package’s eslint.config.mjs:

{
  languageOptions: {
    parserOptions: {
      project: './tsconfig.json',  // Explicit relative path
      tsconfigRootDir: __dirname,
    },
  },
}

Gotcha 3: VS Code’s TypeScript Version vs. Your Project’s TypeScript Version

VS Code bundles its own version of TypeScript for the language server. Your project installs TypeScript as a dev dependency. These versions can differ — VS Code’s bundled version might be older or newer than your project’s.

This causes subtle issues: a feature available in TypeScript 5.5 might not show IntelliSense hints if VS Code’s bundled TypeScript is 5.3.

Fix: tell VS Code to use your project’s TypeScript version:

Cmd+Shift+P → "TypeScript: Select TypeScript Version"
→ "Use Workspace Version"

Or set it in .vscode/settings.json:

{
  "typescript.tsdk": "node_modules/typescript/lib"
}

This should be in every project’s .vscode/settings.json. It ensures every developer uses the same TypeScript version as the CI build.

Gotcha 4: Extensions Are Not Enabled in All Workspaces by Default

Some extensions ask whether they should be enabled globally or per-workspace when installed. Others are enabled globally by default. For security-sensitive extensions (like anything that reads your files), you may want per-workspace control.

If an extension is installed but not working in a specific project, check Cmd+Shift+P → “Extensions: Show Installed Extensions” and verify the extension is enabled for the current workspace, not just globally.

For the ESLint extension specifically: it must have a valid eslint.config.mjs (or legacy .eslintrc) in the project to show any errors. If the config file is missing, the extension is silently inactive.

Hands-On Exercise

Set up VS Code from scratch for a TypeScript project using only committed configuration files.

Step 1: Install the extensions

Run the code --install-extension commands from the Essential Extensions section above. After installing, verify each extension appears in the Extensions panel.

Step 2: Create the workspace configuration files

In a TypeScript project you are working on, create:

.vscode/
├── settings.json      (from the template above)
├── extensions.json    (from the template above)
├── launch.json        (from the appropriate template for NestJS or Next.js)
└── tasks.json         (from the template above)

Commit these four files. Note that .vscode/ is typically not in .gitignore — it should be committed for team standardization.

Step 3: Verify the configuration

Open a TypeScript file and:

  1. Introduce a deliberate formatting error (wrong indentation). Save the file. Prettier should fix it.
  2. Introduce a deliberate ESLint violation (e.g., an unused variable). Verify the red underline appears inline (Error Lens).
  3. Set a breakpoint in a function and press F5. Verify the debugger stops at the breakpoint.

Step 4: Test the task runner

Ctrl+Shift+P → “Tasks: Run Task” → run pnpm: test. Verify the test output appears in the terminal panel.

Step 5: Share with the team

In the project’s README, add a one-line note that .vscode/ is committed and what it provides. Paste the code --install-extension commands into your CONTRIBUTING.md or project README so new team members can get set up in one step.

Quick Reference

Files to Commit to Every TypeScript Project

FilePurpose
.vscode/settings.jsonFormatting, ESLint, TypeScript preferences
.vscode/extensions.jsonExtension recommendations
.vscode/launch.jsonDebug configurations
.vscode/tasks.jsonRunnable tasks (build, test, migrate)
.prettierrcPrettier formatting rules
eslint.config.mjsESLint rule configuration
tsconfig.jsonTypeScript compiler options

Essential Settings Quick Reference

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.updateImportsOnFileMove.enabled": "always"
}

Extension ID Reference

ExtensionID
ESLintdbaeumer.vscode-eslint
Prettieresbenp.prettier-vscode
Error Lensusernamehw.errorlens
GitLenseamodio.gitlens
GitHub Copilotgithub.copilot
GitHub Copilot Chatgithub.copilot-chat
GitHub Pull Requestsgithub.vscode-pull-request-github
Path IntelliSensechristian-kohler.path-intellisense
Prismaprisma.prisma
Vue - Officialvue.volar
Tailwind CSS IntelliSensebradlc.vscode-tailwindcss
ES7+ React Snippetsdsznajder.es7-react-js-snippets

Further Reading