Building tools. Learning to build tools. Learning to build learning tools.
How to merge colors into workbench.colorCustomizations without clobbering other extensions.
VS Code stores workspace-level UI colors in a single JSON object at
workbench.colorCustomizations inside .vscode/settings.json.
Multiple extensions (and the user) might be writing to this same object. If Color Identity
naively overwrites the entire object, it destroys other extensions’ customizations.
If it only adds its own keys, it needs a way to cleanly remove them later.
colorApplier.ts solves both problems with a managed keys pattern.
const MANAGED_KEYS: readonly (keyof WorkspaceColors)[] = [
'titleBarActiveBackground',
'titleBarActiveForeground',
'titleBarInactiveBackground',
'titleBarInactiveForeground',
'activityBarBackground',
'activityBarForeground',
'activityBarActiveBorder',
'statusBarBackground',
'statusBarForeground',
'tabsBackground',
];
This array is the extension’s claim: “These are the keys I manage. I will add them,
update them, and remove them. I will not touch anything else.” The type annotation
(keyof WorkspaceColors)[] ensures this list stays in sync with the
WorkspaceColors interface from types.ts — if a field is added
or removed from the interface, TypeScript catches the inconsistency.
const KEY_MAP: Record<keyof WorkspaceColors, string> = {
titleBarActiveBackground: 'titleBar.activeBackground',
titleBarActiveForeground: 'titleBar.activeForeground',
titleBarInactiveBackground: 'titleBar.inactiveBackground',
titleBarInactiveForeground: 'titleBar.inactiveForeground',
activityBarBackground: 'activityBar.background',
activityBarForeground: 'activityBar.foreground',
activityBarActiveBorder: 'activityBar.activeBorder',
statusBarBackground: 'statusBar.background',
statusBarForeground: 'statusBar.foreground',
tabsBackground: 'editorGroupHeader.tabsBackground',
};
VS Code uses dotted key names like titleBar.activeBackground in its configuration.
TypeScript property names can’t contain dots, so the extension uses camelCase internally.
This map translates between the two worlds. It exists in one place, used by both
applyColors and resetColors.
The key editorGroupHeader.tabsBackground is worth noting — it’s not
tabBar.background as you might expect. VS Code’s color key naming follows
its internal component hierarchy, which doesn’t always match the user-facing terminology.
When building an extension that writes color customizations, you need to consult the
Theme Color Reference
for the exact key names.
export async function applyColors(colors: WorkspaceColors): Promise<void> {
const config = vscode.workspace.getConfiguration();
const existing = config.get<Record<string, string>>(
'workbench.colorCustomizations'
) ?? {};
const updated = { ...existing };
for (const key of MANAGED_KEYS) {
const vscodeKey = KEY_MAP[key];
const value = colors[key];
if (value !== undefined) {
updated[vscodeKey] = value;
} else {
// If the color isn't set (element not affected), remove our old value
delete updated[vscodeKey];
}
}
await config.update(
'workbench.colorCustomizations',
Object.keys(updated).length > 0 ? updated : undefined,
vscode.ConfigurationTarget.Workspace
);
}
The merge strategy:
?? {} handles the case where
no customizations exist yet.
{ ...existing } creates a shallow copy so we
don’t mutate the original object.
MANAGED_KEYS are left completely untouched.
undefined to remove the
setting entirely (keeping settings.json clean). The ConfigurationTarget.Workspace
ensures we only write to the workspace, never to the user’s global settings.
Always use ConfigurationTarget.Workspace when writing color customizations.
Writing to Global would affect every workspace the user opens, which defeats
the purpose of per-workspace identity. This is a common mistake in color-theming extensions.
export async function resetColors(): Promise<void> {
const config = vscode.workspace.getConfiguration();
const existing = config.get<Record<string, string>>(
'workbench.colorCustomizations'
) ?? {};
const updated = { ...existing };
for (const key of MANAGED_KEYS) {
delete updated[KEY_MAP[key]];
}
await config.update(
'workbench.colorCustomizations',
Object.keys(updated).length > 0 ? updated : undefined,
vscode.ConfigurationTarget.Workspace
);
}
Reset follows the same pattern but only deletes. Every managed key is removed; everything else
survives. If the result is empty, the entire workbench.colorCustomizations key
is removed from settings.json.
The managed-keys pattern is a general solution for any extension that writes to shared
configuration. The idea: declare which keys you own, iterate only over those, and leave
the rest alone. It’s simple, correct, and easy to verify by inspection. The same
pattern works for other shared VS Code settings like editor.tokenColorCustomizations.
Colors are now being generated (Section 4) and applied to workspace settings (this section). Next, we’ll build the color picker UI that lets users override the automatic hue with a visual selection experience.