on this page
- Extensions: teaching the editor about your tools
- Format on save: stop hand-formatting forever
- User settings vs workspace settings: the one that confuses everyone
- Themes and icons, briefly
- Debugger basics: stop console.logging everything
- Why your editor, your project, and CI all have to agree
- Common mistakes
- The point of all this
A fresh install of VS Code (Visual Studio Code, the editor most web developers use) is a blank room with the lights on. It opens files. It has a dark theme. That’s about it. The version a working dev uses looks almost the same on the surface, but it’s quietly catching your mistakes, formatting your code while you save, and showing you who last touched the line you’re about to break.
The gap between those two is maybe twenty minutes of setup. This guide walks you through it: which add-ons are actually worth installing, how to stop hand-formatting your code forever, and the one settings distinction that quietly makes a whole team format the same way. None of it is magic. It’s just configuration that nobody handed you on day one.
Extensions: teaching the editor about your tools
Out of the box, VS Code knows how to edit text and color some syntax. It does not know that your project uses ESLint, or that you’d like your code formatted a certain way, or anything about Docker. Extensions are add-ons that teach the editor about specific tools. You install them once, and the editor gets smarter about that thing.
You install them from the Extensions panel (the icon that looks like four squares with one flying off, or Ctrl+Shift+X). Search, click Install, done. Here’s the short list that earns its place on a real project, and why.
| Extension | What it does | When you need it |
|---|---|---|
| ESLint | Shows lint errors and warnings inline as you type, with red and yellow squiggles | Any JavaScript or TypeScript project that uses ESLint |
| Prettier | Formats your code (spacing, quotes, line breaks) to one consistent style | Almost always; pairs with format on save |
| Error Lens | Prints the error text on the line itself instead of only in the Problems panel | Always; it turns “go hunt for the error” into “the error is right there” |
| GitLens | Shows git blame and history in the gutter: who changed this line, when, and why | When you work with other people, or past you |
| Docker | Manages containers from the sidebar and helps you write Dockerfiles | Any project with Docker; see the Docker guide for the why |
| REST Client / Thunder Client | Calls your API from inside the editor, no separate app needed | When you’re building or poking at an HTTP API |
| Astro / Angular Language Service | Understands your framework’s files: syntax, autocomplete, errors | One per stack, matched to whatever you build with |
A few of those deserve a sentence more.
ESLint and Prettier do different jobs and people constantly confuse them. ESLint catches problems (an unused variable, a missing await, a thing that’s probably a bug). Prettier only cares how the code looks: indentation, quote style, where the line wraps. ESLint is the spell-checker; Prettier is the typesetter. You want both, and they stay out of each other’s way once configured.
Error Lens sounds like a small thing and changes your whole day. Normally an error lives in the Problems panel at the bottom, which you have to look at on purpose. Error Lens drags the message up next to the code that caused it. You stop missing things.
GitLens answers the question you ask out loud at least once a week: “who wrote this and what were they thinking?” It puts a faint note at the end of the current line showing the last commit that touched it, and lets you open the full history without leaving the file.
For the REST client, you’ve got two reasonable picks. .http file you can commit to the repo, which is nice because the next person gets your example requests for free. Either one means you can hit your Express route or check a Railway-hosted endpoint without alt-tabbing to another program.
(Those links give you a heads-up before they bounce you off the site.)
The language-specific extension is the one people forget. If you write Astro, install the Astro extension and your .astro files light up with real autocomplete and error checking. If you write Angular, the Angular Language Service does the same for templates. Without it, the editor treats your framework’s files as vaguely-colored text and you wonder why nothing autocompletes. Install the one for your stack and move on.
Format on save: stop hand-formatting forever
Here is the single setting that pays for this entire guide. Format on save means the editor runs your formatter, Prettier, every single time you hit save. You type messy code, you press Ctrl+S, and it snaps into clean shape. You stop manually fixing indentation. You stop arguing with yourself about whether that object should be on one line.
To turn it on, open your settings as JSON. Hit Ctrl+Shift+P, type “Open User Settings (JSON)”, and add this:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
The first line tells VS Code to format every time you save. The second names Prettier as the tool that does the formatting, because the editor needs to know which formatter you mean when more than one is installed. That esbenp.prettier-vscode string is Prettier’s extension ID, not something you make up.
If you only want this for certain file types, you can scope it, which is handy when one formatter misbehaves on, say, Markdown:
{
"editor.formatOnSave": true,
"[markdown]": {
"editor.formatOnSave": false
}
}
The tradeoff is small but real: on a giant file, formatting on every save adds a beat of delay, and if your Prettier config disagrees with what’s already in the repo, your first save on an old file will reformat the whole thing and clutter your diff. Both are solved by everyone sharing one config, which is the next section, and the actual point of this guide.
User settings vs workspace settings: the one that confuses everyone
VS Code has two places settings can live, and mixing them up is behind half the “but it works on my machine” formatting fights.
User settings apply to you, everywhere, on every project you open. They live in your VS Code profile, tucked away in your home folder. Your dark theme, your font size, your preference for a particular keybinding: that’s all user settings. They follow you from project to project, which is exactly what you want for personal taste.
Workspace settings (also called project settings) apply only to the one project they live in. They sit in a file at .vscode/settings.json inside the repo. When you open that project, VS Code reads them and they win over your user settings for that project. When you close it, they stop mattering.
Here’s why this distinction earns a whole section. If you set format on save only in your user settings, it works great for you and nobody else. Your teammate opens the same repo, saves a file, and nothing formats, because the setting lives in your profile, not in the project. Now two people are formatting two different ways and every pull request fills up with pointless whitespace changes.
The fix is to put the shared rules in the project and commit them. A .vscode/settings.json like this travels with the repo:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
The first two lines you’ve seen. The third tells VS Code to also run ESLint’s autofix on save, so fixable lint problems (a stray semicolon, an import in the wrong order) get cleaned up the moment you save, the same way for everyone who opens the project.
You can take it one step further and recommend the extensions themselves, so a new contributor opening the repo gets a gentle “this project suggests installing these” prompt. That lives in .vscode/extensions.json:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"astro-build.astro-vscode"
]
}
Now the project itself tells people what to install. Onboarding goes from a paragraph in the README to clicking “Install All.”
Themes and icons, briefly
You will spend a lot of hours looking at this editor, so picking a theme you like is allowed and even sensible. The Themes picker (Ctrl+K Ctrl+T) cycles through what you have installed. A file-icon theme like Material Icon Theme puts a recognizable little icon next to each file type, which makes a crowded sidebar scannable at a glance. This is the one part of setup that’s pure preference. Pick what’s easy on your eyes and don’t overthink it. It lives in user settings, because it’s about you, not the project.
Debugger basics: stop console.logging everything
When something’s wrong, the reflex is to scatter console.log everywhere, rerun, read the output, and repeat. It works, barely, and it’s slow. VS Code has a real debugger built in, and for Node.js it takes almost no setup.
A breakpoint is a marked line where execution pauses so you can look around. You click in the gutter just left of a line number and a red dot appears. Run your program under the debugger and it stops there, letting you inspect every variable’s current value, see how you got to this line, and step forward one line at a time.
For anything beyond a single file, VS Code reads a launch.json that describes how to start your program. It lives at .vscode/launch.json. A minimal one for a Node app:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug server",
"program": "${workspaceFolder}/src/index.js"
}
]
}
type says you’re debugging Node. request: "launch" means VS Code starts the program itself (as opposed to attaching to one that’s already running). name is just the label you’ll see in the Run panel. program points at your entry file, and ${workspaceFolder} is a variable VS Code fills in with your project’s root path so the config works on anyone’s machine.
Set a breakpoint, hit F5, and you’re stepping through real execution instead of guessing from log lines. That’s the whole idea here; for the full mental model of stepping, the call stack, watch expressions, and debugging code that’s already running, the debugging guide goes deep.
Why your editor, your project, and CI all have to agree
This is the part that ties the whole setup together, so stay with me for a minute.
Prettier and ESLint don’t only run in your editor. They almost always run again in CI (Continuous Integration, the checks that run automatically on GitHub when you open a pull request). CI runs the exact same tools against your code and fails the check if the formatting is off or a lint rule is broken. If you’re fuzzy on what CI is doing, what CI/CD actually does lays it out.
Now picture the failure mode. Your editor formats with one set of rules. CI checks with a different set. You save a file, it looks clean to you, you push it, and CI rejects it because by its rules the formatting is wrong. So you tweak it, push again, fail again on something else. That’s format churn: an endless loop of whitespace-only commits and red checks, and it is genuinely maddening.
The cause is always the same. Three different places have three different opinions about your code. The fix is to give them one shared opinion: commit a .prettierrc and an ESLint config to the repo, point your editor’s extensions at that committed config (which they do by default when the file is in the project), and make CI run the same config. One source of truth, three readers. Your editor agrees with the project, the project agrees with CI, and the loop disappears.
# What CI runs is the same thing your editor and pre-commit should run
npx prettier --check .
npx eslint .
That first command checks every file against your committed Prettier config and exits with an error if anything’s mis-formatted. The second runs ESLint across the project. If those pass on your machine, they pass in CI, because they’re reading the same rules. When the editor and CI share config, the editor becomes an early warning system instead of a source of surprises.
Common mistakes
These are the ones that turn a calm editor into a daily annoyance.
| Mistake | What happens | Fix |
|---|---|---|
| Format settings only in user settings | Works for you, teammates format differently, PRs fill with whitespace | Move shared rules into a committed .vscode/settings.json |
No committed .vscode/settings.json | Every contributor’s editor behaves differently | Commit project settings so the team shares one setup |
| Format on save turned off | PRs become whitespace soup; reviewers can’t see real changes | Set editor.formatOnSave to true and commit it |
| Editor rules disagree with CI | Endless format churn and failed checks on green-looking code | Commit one Prettier and ESLint config; editor and CI both read it |
| Fifty extensions installed reflexively | Slow startup, mystery conflicts | Install what you use; add the rest on demand |
The point of all this
A blank editor still ships code. So does a fast hatchback with no mirrors, technically. The reason to spend twenty minutes here is that a configured editor removes a hundred tiny decisions from your day: how to format this line, where that error went, whether your code will pass CI. Those decisions stop reaching your conscious brain, and what’s left is the actual work.
When the editor agrees with the project and the project agrees with CI, pushing code stops being a coin flip. You write, you save, it formats, you push, the checks go green, and the thing goes live. A calm editor is upstream of every project you actually finish, which is the whole reason any of this setup matters.