Building tools. Learning to build tools. Learning to build learning tools.
How package.json declares everything VS Code needs to know about an extension.
Every VS Code extension starts with package.json. It serves double duty: it’s both
a standard npm package manifest and the extension’s declaration to VS Code. The editor reads this
file to know when to activate your extension, what commands it provides, and what settings the user
can configure.
Here’s the Color Identity manifest, annotated:
{
"name": "color-identity",
"displayName": "Color Identity",
"description": "Theme-aware workspace coloring — distinguish VS Code windows
with colors that harmonize with your active color theme.",
"version": "0.1.0",
"publisher": "bojordan",
"engines": {
"vscode": "^1.85.0"
},
"activationEvents": [
"onStartupFinished"
],
"main": "./out/extension.js"
}
The engines.vscode field sets the minimum VS Code version. Extensions that use newer
APIs must declare the version that introduced them. Color Identity uses ^1.85.0
because the APIs it relies on (QuickPick icons, status bar items, color theme detection)
have been stable since at least that version.
VS Code doesn’t load every installed extension on startup. Instead, each extension declares
activation events — conditions under which VS Code should load and call the extension’s
activate() function.
Color Identity uses onStartupFinished:
"activationEvents": [
"onStartupFinished"
]
This means: “activate this extension after VS Code has finished all its startup work.”
It’s more polite than * (which activates immediately and can delay startup)
but still runs automatically without requiring the user to invoke a command first.
For a coloring extension, onStartupFinished is the right call. The user wants colors
applied as soon as the editor is usable, without having to run a command every time they open
a workspace. But because we wait for startup to finish, we don’t slow down the initial
window render.
Commands are the primary way users interact with extensions. They show up in the Command Palette
(Cmd+Shift+P) and can be bound to keyboard shortcuts.
Color Identity declares four:
"contributes": {
"commands": [
{
"command": "colorIdentity.chooseColor",
"title": "Color Identity: Choose Color…"
},
{
"command": "colorIdentity.applyColors",
"title": "Color Identity: Apply Colors"
},
{
"command": "colorIdentity.resetColors",
"title": "Color Identity: Reset Colors"
},
{
"command": "colorIdentity.refreshColors",
"title": "Color Identity: Refresh Colors for Current Theme"
}
]
}
A few conventions worth noting:
colorIdentity..
This prevents collisions with other extensions and makes it easy to find your commands
in logs or when searching.
Extensions can declare settings that appear in VS Code’s Settings UI (and in
settings.json). Color Identity declares seven:
"configuration": {
"title": "Color Identity",
"properties": {
"colorIdentity.enabled": {
"type": "boolean",
"default": true,
"description": "Automatically apply identity colors when a workspace opens."
},
"colorIdentity.affectTitleBar": {
"type": "boolean",
"default": true,
"description": "Colorize the title bar."
},
"colorIdentity.affectActivityBar": {
"type": "boolean",
"default": true,
"description": "Colorize the activity bar."
},
"colorIdentity.affectStatusBar": {
"type": "boolean",
"default": true,
"description": "Colorize the status bar."
},
"colorIdentity.affectTabBar": {
"type": "boolean",
"default": false,
"description": "Colorize the editor tab bar background."
},
"colorIdentity.saturationAdjustment": {
"type": "number",
"default": 0,
"minimum": -50,
"maximum": 50,
"description": "Fine-tune saturation (-50 to +50)."
},
"colorIdentity.lightnessAdjustment": {
"type": "number",
"default": 0,
"minimum": -30,
"maximum": 30,
"description": "Fine-tune lightness (-30 to +30)."
},
"colorIdentity.hueOverride": {
"type": ["number", "null"],
"default": null,
"minimum": 0,
"maximum": 360,
"description": "Override the auto-detected hue (0–360). Null for automatic."
}
}
}
This schema does several useful things automatically:
minimum/maximum constraints are enforced before the extension ever sees the value.default values mean the extension works out of the box with no configuration.
Notice that affectTabBar defaults to false. Coloring the tab bar
can feel heavy-handed — it’s a lot of visual real estate and affects readability
of tab labels. The option exists for users who want it, but the default stays conservative.
Sensible defaults are part of good extension design: the user should get a good experience
without touching settings.
The scripts section defines the build pipeline:
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"lint": "eslint src --ext ts"
}
compile runs the TypeScript compiler, outputting JavaScript to the out/ directory
(which is where "main": "./out/extension.js" points). watch does the same but
re-compiles on every file save. vscode:prepublish is special — it runs automatically
when you package the extension with vsce package.
To debug: press F5 in VS Code. This launches a second VS Code window (the
“Extension Development Host”) with your extension loaded. You can set breakpoints
in your TypeScript source files, inspect variables, and step through code. The .vscode/launch.json
and .vscode/tasks.json files configure this workflow.
You now understand the extension’s external contract — everything VS Code knows about Color Identity before a single line of TypeScript runs. Next, we’ll look at the types and configuration reader that form the internal contract between the extension’s modules.