Create, develop, and publish VS Code extensions.
For extensions with a management UI, default to a dedicated Activity Bar icon
and sidebar unless another entry point is explicitly chosen. A Marketplace
icon alone does not create that navigation; use the TreeView reference below.
For user-facing extensions, provide a clearly labeled bug-report/feature-request entry in the primary sidebar or management UI and a Command Palette fallback; a README or Marketplace link alone is insufficient. Preview minimal, non-sensitive metadata before opening a fixed feedback destination; leave submission to the user and never auto-attach prompts, raw logs, secrets or private paths.
When to Use
- VS Code extension, extension development, vscode plugin
- Creating a new VS Code extension from scratch
- Adding commands, keybindings, or settings to an extension
- Publishing to VS Code Marketplace
Quick Start
# Scaffold new extension (recommended)
npm install -g yo generator-code
yo code
# Or minimal manual setup
mkdir my-extension && cd my-extension
npm init -y && npm install -D typescript @types/vscode
Project Structure
my-extension/
├── package.json # Extension manifest
├── src/extension.ts # Entry point
├── out/ # Compiled JS (gitignore)
├── artifacts/vsix/ # Keep local VSIX archives out of the repo root
├── images/icon.png # 128x128 PNG for Marketplace
└── .vscodeignore # Exclude files from VSIX
Building & Packaging
npm run compile # Build once
npm run watch # Watch mode (F5 to launch debug)
mkdir -p artifacts/vsix
npx @vscode/vsce package --out artifacts/vsix/my-extension-1.0.0.vsix
Keep local .vsix archives under artifacts/vsix/ instead of the repository root, and prune old local builds on a schedule so release artifacts do not pile up.
Done Criteria
- The packaged VSIX installs and activates in an isolated profile; changed user-facing integrations complete their real workflow there
- Primary sidebar entry and commands work; referenced icons are present in the VSIX
- Package size < 5MB (use
.vscodeignore) - README links and feedback UI reach the intended support destination; verify the UI-to-form path with synthetic data without submitting
- Local VSIX artifacts stored outside the repo root and pruned regularly
Quick Troubleshooting
| Symptom | Fix |
|---|---|
| Extension not loading | Check main/engines.vscode; contributed commands/views activate implicitly (1.74+), add activationEvents only for other triggers |
| Command not found | Match command ID in package.json/code |
| Shortcut not working | Remove when clause, check conflicts |
Reference Map
| Topic | Reference |
|---|---|
| AI Customization | references/ai-customization.md |
| Code Review Prompts | references/code-review-prompts.md |
| Code Samples | references/ai-customization.md and references/webview.md |
| TreeView | references/treeview.md |
| Webview | references/webview.md |
| Testing | references/testing.md |
| Publishing | references/publishing.md |
| Troubleshooting | references/troubleshooting.md |
| Notifications | references/notification-normalization.md |
Best Practices
Extension Host boundary
- Scanner / provider / TreeView running on the Extension Host should, when they can do the same thing, prefer the VS Code API over Node-specific
path/Buffer/ rawfs. This makes it easier to avoid environmental differences between Problems and the actual build. - For resources bundled with your own extension, do not guess the user's home directory or the VS Code installation location; resolve them from the extension context, such as
context.extensionUriとvscode.Uri.joinPath. - When you need to read resources bundled with another installed extension, also prioritize the known roots of
resources/agents|skills|prompts|instructions|hooks|mcpand thechatAgents/chatPromptFilesdeclaration in the manifest. Treat them as read-only resources separate from built-in resources, and do not mix in uninstall or reinstall flows. - Do not scatter runtime diagnostic logs across
console.log; consolidate them into an Output Channel-based logger. Also provide a way for users to open the logs somewhere, such as a command, notification, or the README. - Do not synchronously parse or aggregate large inputs on the Extension Host thread. Pass bounded bytes to
node:worker_threads, and on cancel / opt-out completeawait worker.terminate()and listener removal, and update the cache / UI only with results whose generation ID matches. - Only transfer exclusively owned, non-
SharedArrayBufferdata, and after transfer do not use the sender-side view (all views of the same backing store are detached).Buffermay share a pool, so for offset views or buffers with ambiguous ownership, copy only the necessary range into a separate buffer and pass that. - Do not cache or display results received from a worker or another process as-is. Perform allowed-key checks with
Reflect.ownKeys, and check for the existence of required keys withObject.hasOwn(Object.keysmisses non-enumerable own properties, whileinand plain property access let inherited values through). - Do not output the exception's
Error.nameやFileSystemError.codeas-is to logs / UI. Normalize it to an allowlist of known codes, and map unknown ones to a single generic code. This prevents cases where paths or tokens get mixed into names.
Manifest / Docs / Localization
package.jsonWhen you change the commands, views, configuration, or menus of- For extensions that localize the Marketplace display or setting descriptions, update
package.nls.jsonand the target language'spackage.nls.*.jsonin the same change. - In
markdownDescription, use native setting references such as`#editor.wordWrap#`, not[label](#editor.wordWrap#): the latter can leave a trailing hash in the Settings@id:filter. Guard each locale's syntax and verify the resolved target. - If you change the order or descriptions of settings, review the README settings table, the manifest consistency test, and whether release notes are needed, all together.
Language Model Tools
- Extension-provided LM Tools need strong
modelDescriptionintent phrases. PreferUse when the user asks to ...plus natural-language verbs for create/update/delete/toggle paths, and add a manifest/doc guard so future wording changes do not silently weaken agent-mode tool selection. - For configuration from VS Code Chat, prefer native LM Tools when no external client is required; do not call them an external MCP server. Share validated domain operations with the GUI, expose only necessary actions, and use explicit enable/disable values rather than retry-sensitive toggles.
- Keep
prepareInvocationside-effect free and request confirmation for mutations or sensitive disclosure. Revalidate input, cancellation, trust and the queried revision inside the locked write after confirmation. Host approval UI/policies still apply; custom confirmation text is not a bypass. Never accept secret values through tool arguments or silently widen execution permissions. - Page query results and omit prompt, environment and raw-output payloads by default. Make sensitive detail retrieval explicit with disclosure confirmation; describe which metadata reaches the model and treat stored strings as data, not instructions.
- Return typed outcomes from shared operations; a GUI handler that catches errors and returns nothing cannot prove a tool succeeded. Keep committed IDs/success when only presentation fails, return a sanitized warning and tell the caller to query rather than repeat the mutation. Configuration saved is not work executed.
- Before publishing an extension with LM Tools, inspect the packaged VSIX rather than trusting source files: confirm
extension/package.jsonhas the intended version, expectedcontributes.languageModelToolscount, and nosrc/, test payloads, or sourcemaps unless intentionally shipped.
Generated Sections
START/ENDTreat the generated section enclosed by markers as a single SSOT.- If you find duplicate marker pairs, do not keep both and append; merge the content and restore a single marker pair.
Naming consistency
Before publishing, unify the package name, setting keys, and command names:
| Item | Example |
|---|---|
| Package name | copilot-scheduler |
| Setting key | copilotScheduler.enabled |
| Command ID | copilotScheduler.createTask |
| View ID | copilotSchedulerTasks |
Centralized notification management
Notifications are consolidated into a common helper, and configuration values are normalized to known enums at runtime. For duplicate suppression, separation from logging, actions, and security design, refer to Notification Normalization .