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 ColorIdentity manifest, annotated:
{
"name": "color-identity",
"displayName": "ColorIdentity",
"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. ColorIdentity 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.
ColorIdentity 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.
ColorIdentity declares four:
"contributes": {
"commands": [
{
"command": "colorIdentity.chooseColor",
"title": "ColorIdentity: Choose Color…"
},
{
"command": "colorIdentity.applyColors",
"title": "ColorIdentity: Apply Colors"
},
{
"command": "colorIdentity.resetColors",
"title": "ColorIdentity: Reset Colors"
},
{
"command": "colorIdentity.refreshColors",
"title": "ColorIdentity: 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). ColorIdentity declares nine:
"configuration": {
"title": "ColorIdentity",
"properties": {
"colorIdentity.enabled": {
"type": "boolean",
"default": true,
"description": "Automatically apply identity colors when a workspace opens."
},
"colorIdentity.colorMode": {
"type": "string",
"enum": ["simple", "harmonized"],
"default": "harmonized",
"description": "Color selection mode. 'simple' uses basic theme detection;
'harmonized' generates options that integrate with your theme."
},
"colorIdentity.affectTitleBar": {
"type": "boolean",
"default": true,
"description": "Colorize the title bar."
},
"colorIdentity.affectActivityBar": { ... },
"colorIdentity.affectStatusBar": { ... },
"colorIdentity.affectTabBar": {
"type": "boolean",
"default": false,
"description": "Colorize the editor tab bar background."
},
"colorIdentity.saturationAdjustment": { ... },
"colorIdentity.lightnessAdjustment": { ... },
"colorIdentity.hueOverride": {
"type": ["number", "null"],
"default": null,
"description": "Fixed hue (0–360). Null for automatic."
},
"colorIdentity.harmonyOffset": {
"type": ["number", "null"],
"default": null,
"minimum": -180,
"maximum": 180,
"description": "Offset from theme base hue. Set automatically by the
picker in harmonized mode."
}
}
}
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.
Two settings deserve special attention. colorMode defaults to
"harmonized" — the more sophisticated mode is the better default
experience, while "simple" is there for users who prefer the original
fixed-preset behavior. And harmonyOffset is set automatically
by the picker, not by the user directly. It stores the angular relationship to the
theme (e.g., +25° for analogous, +180° for complement) so colors can adapt
when the theme changes. We’ll see how this works in Sections 4 and 8.
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 ColorIdentity 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.