130 Widgets

Building tools. Learning to build tools. Learning to build learning tools.

1. Extension Anatomy

How package.json declares everything VS Code needs to know about an extension.

The Extension Manifest

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"
}
VS Code Concept

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.

Activation Events

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.

Tip

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

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:

Configuration Schema

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:

Design Decision

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.

Build and Debug

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.

Checkpoint

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.