VS Code Productivity Extensions
Key takeaways
The right VS Code extensions and settings eliminate an entire category of daily friction: formatting debates, missed errors, slow navigation. This guide covers the essential extensions for JavaScript and TypeScript development, plus the settings that make them work well together.
Why Extension Choice Matters
VS Code ships as a lightweight editor. The extension ecosystem is what makes it a productive IDE — but it is easy to go wrong by installing too many or the wrong ones. The most common failure modes:
- Too many extensions: slow startup, conflicting formatters, confusing settings
- Formatter conflicts: ESLint and Prettier fighting over the same file
- Wrong TypeScript version: VS Code using its bundled version instead of your project’s version
This guide covers a minimal, high-impact set that covers the daily workflow for JavaScript and TypeScript development.
Core Extensions
ESLint (dbaeumer.vscode-eslint)
ESLint integration shows lint errors inline as you type — before you save, before CI runs:
- Red squiggles under actual code problems (unused variables, missing dependencies in
useEffect, etc.) - Respects your project’s
.eslintrcoreslint.config.js - Auto-fix on save when configured (see Settings section)
Important: open the folder that contains package.json as the workspace root. ESLint resolves plugins and rules relative to the closest config file — if VS Code opens a parent directory, it may not find your project’s configuration.
The extension does not bundle ESLint; it loads the eslint package from your project’s node_modules. That is deliberate — the editor then uses exactly the version and plugins your CI uses — but it means nothing appears until npm install has run, and in a monorepo each package may resolve a different ESLint. For monorepos, the eslint.workingDirectories setting (for example [{ "mode": "auto" }]) tells the extension to treat each folder with its own config as a separate root. When lint errors silently stop appearing, the ESLint entry in the Output panel is the first place to look; it logs which config it loaded and any plugin that failed to resolve.
Prettier (esbenp.prettier-vscode)
Prettier handles consistent formatting so your team never debates tabs vs spaces, quote styles, or trailing commas:
- Formats on save when
editor.formatOnSaveis enabled - Reads
.prettierrcorprettier.config.jsfrom your project root - Works across JS, TS, JSON, CSS, HTML, Markdown
Prettier + ESLint together: install eslint-config-prettier in your project to turn off ESLint’s formatting rules:
npm install -D eslint-config-prettier
// legacy .eslintrc.json
{
"extends": ["...", "prettier"]
}
// eslint.config.js (flat config, the default since ESLint 9)
import eslintConfigPrettier from "eslint-config-prettier";
export default [
// ...your other configs
eslintConfigPrettier, // keep it last so it can switch off conflicting rules
];
Now ESLint handles code quality, Prettier handles formatting, no conflicts.
eslint-config-prettier adds no rules; it only turns off rules that concern formatting (indentation, quotes, semicolons, line length), which is why it must come last — a later config could turn those rules back on. The flat-config form matters because ESLint 9 no longer reads .eslintrc files by default, so copying the "extends" snippet from an older tutorial into eslint.config.js does nothing, or fails with a config validation error. The older approach of running Prettier inside ESLint via eslint-plugin-prettier also works, but it reports every formatting difference as a lint error and is noticeably slower; letting Prettier format on save and ESLint check logic keeps the two separate.
TypeScript (built-in — configure it right)
TypeScript support is built in, but you need to tell VS Code to use your project’s TypeScript version:
- Open a
.tsfile - Open Command Palette (
Cmd+Shift+P) - Type “TypeScript: Select TypeScript Version”
- Choose “Use Workspace Version”
Your project’s node_modules/typescript now drives type checking. This ensures you get accurate errors based on the TypeScript version your project actually uses.
VS Code asks before switching because running a workspace’s TypeScript means executing code from that repository, and it will not do so for an untrusted folder. The typescript.tsdk setting shown later only points at the workspace version; the prompt to allow it still appears once per workspace. The mismatch is most visible right after a TypeScript release: the editor’s bundled version is usually newer than a project’s pinned one, so it may accept syntax your build rejects, or report errors that tsc in CI does not. When the editor seems stuck on stale errors — after switching branches or regenerating types — “TypeScript: Restart TS Server” is faster than reloading the window.
Quality and Error Visibility
Error Lens (usernamehw.errorlens)
Error Lens prints diagnostic messages at the end of the problem line, inline, without you needing to hover:
const x: string = 42 // Type 'number' is not assignable to type 'string'.
Instead of hunting for underlines and hovering to read error messages, you see the problem immediately. This is the single biggest time-saver in the list for TypeScript development.
EditorConfig (EditorConfig.EditorConfig)
If your project has a .editorconfig file, this extension enforces indent size, charset, and line endings automatically. Particularly useful on cross-platform teams where some developers use Windows and others use macOS.
Its value over Prettier is reach: EditorConfig is understood by JetBrains IDEs, Vim, Visual Studio and many others, and it applies to files Prettier does not format, such as shell scripts, Makefiles and Python. Line endings are the setting that prevents the most pain. A shell script saved with Windows CRLF endings fails in a Linux container with /bin/bash^M: bad interpreter, and a whole-file diff caused by line endings hides the real change in code review. For files already committed, pair it with a .gitattributes entry such as * text=auto eol=lf, since EditorConfig only affects how the editor saves, not how Git checks files out.
# .editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
Navigation and Git
GitLens (eamodio.gitlens)
GitLens adds git context throughout the editor without leaving the file you’re reading:
- Inline blame: see who wrote each line and when, right in the editor
- File history: compare the current file with any previous version
- Branch comparison: diff two branches inline
- Commit search: find when a specific change was made
The built-in Git panel is enough for staging and committing. GitLens is for investigation — understanding why code was written a certain way, who owns a confusing section, or when a bug was introduced.
Path Intellisense (christian-kohler.path-intellisense)
Autocompletes file paths in import statements as you type:
import { something } from './utils/ // shows directory contents as you type
Especially useful in projects with deep directory structures where you might not remember the exact path.
Workflow-Specific Extensions
Add these when they match your actual work — not before:
REST Client (humao.rest-client)
Write HTTP requests in .http files and run them with a click:
### Get user
GET https://api.example.com/users/1
Authorization: Bearer {{token}}
### Create user
POST https://api.example.com/users
Content-Type: application/json
{
"name": "Alice",
"email": "[email protected]"
}
Useful for API development and testing without leaving the editor. Alternative: Thunder Client for a more GUI-like experience.
Docker (ms-azuretools.vscode-docker)
For projects with Docker:
- Dockerfile syntax highlighting and linting
- Manage containers, images, and volumes from the sidebar
- One-click compose up/down for development stacks
DotENV (mikestead.dotenv)
Syntax highlighting for .env files. Simple but makes them easier to read, especially large ones. The extension also helps you notice when a variable value accidentally spans multiple lines.
Keyboard Shortcuts Worth Memorizing
These are built into VS Code — no extensions needed:
| Action | macOS | Windows / Linux |
|---|---|---|
| Command palette | Cmd+Shift+P | Ctrl+Shift+P |
| Quick open file | Cmd+P | Ctrl+P |
| Go to definition | F12 | F12 |
| Peek definition | Opt+F12 | Alt+F12 |
| Go to symbol in file | Cmd+Shift+O | Ctrl+Shift+O |
| Go to symbol in workspace | Cmd+T | Ctrl+T |
| Rename symbol | F2 | F2 |
| Multi-cursor word | Cmd+D | Ctrl+D |
| Toggle terminal | Ctrl+` | Ctrl+` |
| Split editor | Cmd+\ | Ctrl+\ |
| Toggle sidebar | Cmd+B | Ctrl+B |
| Format document | Shift+Opt+F | Shift+Alt+F |
The most valuable shortcut that new VS Code users miss: Cmd+P (Quick Open). Type part of a filename to jump directly to it — faster than navigating the file tree.
Settings JSON
The settings that make the extensions above work well together:
{
// Format on save — Prettier runs when you save
"editor.formatOnSave": true,
// Use Prettier as the default formatter
"editor.defaultFormatter": "esbenp.prettier-vscode",
// ESLint: auto-fix fixable issues on save
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
// TypeScript: workspace version (set per-project)
"typescript.tsdk": "node_modules/typescript/lib",
// Consistent line endings
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
// Error display
"editor.showUnused": true,
"typescript.preferences.includePackageJsonAutoImports": "auto",
// Privacy — disable telemetry (a personal choice; better kept in user settings)
"telemetry.telemetryLevel": "off"
}
Save this in .vscode/settings.json in the project root to share the configuration with the whole team.
"explicit" means the ESLint fixes run when you save explicitly (Ctrl/Cmd+S), not on auto-save after a delay — auto-fixing while you are still typing tends to rewrite code under your cursor. Workspace settings override user settings, which is what makes them useful for team conventions, but that cuts both ways: personal preferences such as telemetry, themes or font size do not belong in a committed settings file. A useful companion is .vscode/extensions.json with a "recommendations" list of extension IDs; VS Code then prompts new team members to install exactly the extensions these settings assume.
Debugging Setup
VS Code’s built-in debugger works well for Node.js with minimal configuration. Add a .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Node.js",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeArgs": ["-r", "ts-node/register"],
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
},
{
"name": "Debug Tests (Jest)",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": [
"jest",
"--runInBand", // run tests serially so debugger works
"--testPathPattern", "${relativeFile}"
],
"console": "integratedTerminal",
"sourceMaps": true
}
]
}
Set breakpoints in TypeScript source files — the source maps handle the translation to compiled JavaScript automatically.
The first configuration compiles TypeScript on the fly with ts-node, so outFiles only matters if you debug compiled output instead; if breakpoints show as “unbound” (hollow grey circles), the debugger could not map them, usually because source maps are disabled in tsconfig.json or outFiles does not match where the compiled files are. Newer projects often replace ts-node with tsx ("runtimeArgs": ["--import", "tsx"]), which starts faster and handles ES modules with less configuration. For Jest, --runInBand matters because Jest otherwise runs tests in worker processes the debugger is not attached to, so breakpoints are never hit. Jest 30 renamed --testPathPattern to --testPathPatterns; on older versions keep the singular form. Often the quickest route is the “JavaScript Debug Terminal”: any node or npm command started in it is debugged automatically, with no launch.json at all.
Common Setup Problems
“Format on save isn’t working”
Check that Prettier is set as the default formatter. If multiple formatters are installed (e.g., a language extension also includes a formatter), VS Code may prompt you to choose. Run “Format Document With…” to see which formatters are available and select Prettier.
“ESLint shows no errors but CI fails”
VS Code uses the ESLint extension; CI runs eslint from the command line. They should both use the same config file, but check that the extension is loading the right config (look at the ESLint output panel for the config path it found). Another frequent cause is scope: the extension lints only the files you open, while CI lints the whole project, so an error in a file nobody opened recently shows up only in CI. Rules that need type information (from typescript-eslint’s type-checked configs) can also differ if the editor and CI use different tsconfig files.
“TypeScript errors in VS Code but not on the command line”
Check which TypeScript version each is using. If tsc uses your project’s TypeScript (from node_modules) but VS Code is using its bundled version, the behavior can differ. Run “TypeScript: Select TypeScript Version” and choose “Use Workspace Version.”
Frequently Asked Questions (FAQ)
Q. Why does format on save undo my ESLint fixes or keep changing the file back and forth?
A. This usually happens when Prettier formats the file and ESLint’s own stylistic rules disagree with it, or when two formatters are registered for the same language. Set Prettier as the editor.defaultFormatter, run ESLint fixes through editor.codeActionsOnSave, and disable ESLint’s formatting rules with eslint-config-prettier. Then each tool has a single job and the file stops flipping between two styles.
Related Articles
- Type Narrowing in TypeScript
- JavaScript Async Programming
- GitHub Actions CI/CD: Workflows, Jobs, Matrix Builds, Secrets and Deployment