// This skill provides expert-level guidance for VS Code extension development. Use when implementing new extension features, debugging extension code, designing WebView UIs, implementing Language Server Protocol features, or optimizing extension performance. Covers activation events, contribution points, VS Code API patterns, security best practices, testing strategies, and publishing workflows.
| name | vscode-extension-expert |
| description | This skill provides expert-level guidance for VS Code extension development. Use when implementing new extension features, debugging extension code, designing WebView UIs, implementing Language Server Protocol features, or optimizing extension performance. Covers activation events, contribution points, VS Code API patterns, security best practices, testing strategies, and publishing workflows. |
This skill enables expert-level VS Code extension development by providing comprehensive knowledge of the VS Code Extension API, architectural patterns, security requirements, and best practices. It should be used when creating new extensions, adding features to existing extensions, implementing WebViews, designing language support, or optimizing performance.
Every VS Code extension requires:
extension-name/
โโโ .vscode/ # Debug configurations
โ โโโ launch.json
โ โโโ tasks.json
โโโ src/
โ โโโ extension.ts # Main entry point
โโโ package.json # Extension manifest (critical)
โโโ tsconfig.json # TypeScript config
โโโ .vscodeignore # Exclude from package
{
"name": "extension-name",
"publisher": "publisher-id",
"version": "0.0.1",
"engines": { "vscode": "^1.80.0" },
"main": "./out/extension.js",
"activationEvents": [],
"contributes": {
"commands": [],
"configuration": {},
"views": {}
},
"extensionKind": ["workspace"]
}
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// Register commands, providers, listeners
const disposable = vscode.commands.registerCommand('ext.command', () => {
// Command implementation
});
context.subscriptions.push(disposable);
}
export function deactivate() {
// Cleanup resources
}
Choose the most specific activation event to minimize startup impact:
| Event | Use Case | Example |
|---|---|---|
onLanguage:<lang> | Language-specific features | onLanguage:python |
onCommand:<command> | Command-driven extensions | onCommand:ext.showPanel |
onView:<viewId> | Sidebar view expansion | onView:myTreeView |
workspaceContains:<glob> | Project-specific features | workspaceContains:**/.eslintrc* |
onFileSystem:<scheme> | Custom file systems | onFileSystem:sftp |
onStartupFinished | Background tasks | (prefer over *) |
Critical: Avoid using * as it activates on every VS Code startup.
{
"contributes": {
"commands": [{
"command": "ext.doSomething",
"title": "Do Something",
"category": "My Extension",
"icon": "$(symbol-method)"
}]
}
}
{
"contributes": {
"configuration": {
"title": "My Extension",
"properties": {
"myExtension.enabled": {
"type": "boolean",
"default": true,
"description": "Enable the extension"
}
}
}
}
}
{
"contributes": {
"views": {
"explorer": [{
"id": "myTreeView",
"name": "My View"
}]
},
"viewsContainers": {
"activitybar": [{
"id": "myContainer",
"title": "My Extension",
"icon": "resources/icon.svg"
}]
}
}
}
// Show messages
vscode.window.showInformationMessage('Hello!');
vscode.window.showErrorMessage('Error occurred');
// Quick picks
const item = await vscode.window.showQuickPick(['Option 1', 'Option 2']);
// Input boxes
const input = await vscode.window.showInputBox({ prompt: 'Enter value' });
// Active editor
const editor = vscode.window.activeTextEditor;
// Read configuration
const config = vscode.workspace.getConfiguration('myExtension');
const value = config.get<boolean>('enabled');
// Watch files
const watcher = vscode.workspace.createFileSystemWatcher('**/*.ts');
watcher.onDidChange(uri => { /* handle change */ });
// Open documents
const doc = await vscode.workspace.openTextDocument(uri);
// Register
const disposable = vscode.commands.registerCommand('ext.cmd', (arg) => {
// Implementation
});
// Execute
await vscode.commands.executeCommand('ext.cmd', argument);
function getWebviewContent(webview: vscode.Webview): string {
const nonce = getNonce();
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src ${webview.cspSource} 'unsafe-inline';
script-src 'nonce-${nonce}';
img-src ${webview.cspSource} https:;
">
</head>
<body>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
// Use vscode.postMessage() for communication
</script>
</body>
</html>`;
}
localResourceRoots// Extension โ WebView
panel.webview.postMessage({ type: 'update', data: payload });
// WebView โ Extension
panel.webview.onDidReceiveMessage(message => {
switch (message.type) {
case 'action':
handleAction(message.data);
break;
}
});
// In WebView JavaScript
window.addEventListener('message', event => {
const message = event.data;
// Handle message
});
vscode.postMessage({ type: 'action', data: result });
// Simple state (survives webview hide/show)
const state = webview.getState() || { count: 0 };
webview.setState({ count: state.count + 1 });
// Full persistence (survives VS Code restart)
class MySerializer implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(panel: vscode.WebviewPanel, state: any) {
panel.webview.html = getHtmlForWebview(panel.webview, state);
}
}
vscode.window.registerWebviewPanelSerializer('myWebview', new MySerializer());
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ Language Client โโโโโโ Language Server โ
โ (VS Code Extension)โ LSP โ (Separate Process) โ
โ vscode-languageclient โ vscode-languageserver
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
const serverOptions: ServerOptions = {
run: { module: serverPath, transport: TransportKind.ipc },
debug: { module: serverPath, transport: TransportKind.ipc }
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'mylang' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.mylang')
}
};
const client = new LanguageClient('mylang', 'My Language', serverOptions, clientOptions);
client.start();
import { createConnection, TextDocuments, ProposedFeatures } from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
connection.onInitialize((params) => {
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: { resolveProvider: true },
hoverProvider: true
}
};
});
connection.onCompletion((params) => {
return [
{ label: 'suggestion1', kind: CompletionItemKind.Text }
];
});
documents.listen(connection);
connection.listen();
class MyTreeDataProvider implements vscode.TreeDataProvider<MyItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<MyItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(element: MyItem): vscode.TreeItem {
return {
label: element.name,
collapsibleState: element.children ?
vscode.TreeItemCollapsibleState.Collapsed :
vscode.TreeItemCollapsibleState.None,
command: {
command: 'ext.selectItem',
title: 'Select',
arguments: [element]
}
};
}
getChildren(element?: MyItem): Thenable<MyItem[]> {
if (!element) {
return Promise.resolve(this.getRootItems());
}
return Promise.resolve(element.children || []);
}
}
// Register
const provider = new MyTreeDataProvider();
vscode.window.registerTreeDataProvider('myTreeView', provider);
// Delay expensive imports
let heavyModule: typeof import('./heavyModule') | undefined;
async function getHeavyModule() {
if (!heavyModule) {
heavyModule = await import('./heavyModule');
}
return heavyModule;
}
Use esbuild for fast bundling:
// esbuild.config.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['./src/extension.ts'],
bundle: true,
outfile: './out/extension.js',
external: ['vscode'],
format: 'cjs',
platform: 'node',
minify: process.env.NODE_ENV === 'production',
sourcemap: true
});
export function activate(context: vscode.ExtensionContext) {
// Always add to subscriptions for automatic cleanup
context.subscriptions.push(
vscode.commands.registerCommand(...),
vscode.window.registerTreeDataProvider(...),
watcher,
client
);
}
export function deactivate() {
// Explicit cleanup for async resources
return client?.stop();
}
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');
module.exports = defineConfig({
files: 'out/test/**/*.test.js',
version: 'stable',
workspaceFolder: './test-fixtures',
mocha: {
timeout: 20000
}
});
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start tests.');
test('Command registration', async () => {
const commands = await vscode.commands.getCommands();
assert.ok(commands.includes('ext.myCommand'));
});
test('Configuration access', () => {
const config = vscode.workspace.getConfiguration('myExtension');
assert.strictEqual(config.get('enabled'), true);
});
});
Cause: Activation events don't match user actions
Solution: Verify activationEvents in package.json match actual triggers
Cause: Missing or incorrect CSP Solution: Always include strict Content-Security-Policy meta tag
Cause: Untracked event listeners or disposables
Solution: Add all disposables to context.subscriptions
Cause: Synchronous heavy operations in activate()
Solution: Use lazy loading and defer non-critical initialization
Cause: Missing contributes.commands declaration
Solution: Ensure command is declared in package.json AND registered with registerCommand
localResourceRoots to necessary pathsincludes())SecretStorage)For detailed reference documentation, see:
references/api-reference.md - Complete VS Code API documentationreferences/webview-security.md - WebView security guidelinesreferences/lsp-guide.md - Language Server Protocol implementation guideFor working examples, reference the official samples: