Ship a VS Code extension to the Marketplace
A 9-step playbook. Open in Dock and you'll get four surfaces seeded:
- **Steps** (table) — the 9 gates as rows, owner + due + status
- **Pointers** (table) — every official Microsoft doc + tool linked from this playbook
- **Brief** (doc) — the canonical write-up you maintain alongside the work
- **Release log** (table) — one row per Marketplace publish (version, vsce build hash, OpenVSX status)
Read `Steps` top-to-bottom on first open. Each row is one of the 9 steps. Click into a step to see the tasks, pointers, and the agent prompt for that step.
Outcome
Your VS Code extension live on the Marketplace AND on Open VSX (so VSCodium / Cursor / Theia users can install too), with CI that publishes a new version on every git tag.
Estimated time: 2-5 days
Difficulty: intermediate
For: Developers shipping their first VS Code extension.
What you'll need
Pre-register or install before you start.
- vsce (Free (npm)) — The official CLI to package + publish .vsix to the Marketplace.
- Yeoman + generator-code (Free (npm)) — Scaffold a new extension with the right tsconfig, .vscodeignore, package.json defaults.
- Azure DevOps (Free) — Where your publisher account lives + where you mint the Personal Access Token.
- Visual Studio Marketplace publisher hub (Free) — Web console for managing publisher metadata, listing, statistics.
- ovsx (Open VSX CLI) (Free) — Cross-publish to Open VSX so VSCodium / Cursor / Gitpod users can install.
The template · 9 steps
Step 1: Scaffold a clean extension with generator-code
Estimated time: 30 min
If you don't already have a working extension, run npx --package yo --package generator-code -- yo code. The generator gives you a tsconfig, esbuild bundling, .vscodeignore (critical for tree-shaking the .vsix), and a sane package.json. Even if you have an existing extension, compare against the generator output — most early extensions miss .vscodeignore tuning.
Tasks
- Run npx --package yo --package generator-code -- yo code
- Pick TypeScript or JavaScript, pick esbuild bundling
- Pick the extension type: command, language, theme, snippets, keymap
- Open the generated folder in VS Code, hit F5 to launch the Extension Development Host
- Verify your command runs in the dev host before doing anything else
Pointers
- [Official] Your first extension
- [Official] Extension manifest reference
[!CAUTION] Gotchas
- The generator's default .vscodeignore is too permissive; review it before first package or your .vsix balloons to 10+ MB.
- Yarn 2+ (Berry) breaks vsce package because of node_modules layout. Use npm or yarn 1.x for extension projects.
Step 2: Create the publisher in Azure DevOps
Estimated time: 30 min
A publisher is the brand you ship under. Publishers are managed in the Visual Studio Marketplace publisher hub but the identity backs onto an Azure DevOps organisation, which is where you mint the Personal Access Token. Pick the publisher ID carefully: it's PERMANENT and shows in every install URL (marketplace.visualstudio.com/items?itemName=publisher.extension).
Tasks
- Sign in at https://dev.azure.com with the Microsoft account you'll use for publishing
- Create or pick an Azure DevOps organisation (free tier is fine)
- Visit https://marketplace.visualstudio.com/manage and Create publisher
- Pick the publisher ID (lowercase, alphanumeric + hyphens, PERMANENT)
- Set the display name + brief description (these can change later)
- Verify the publisher email so the listing can publish
Pointers
- [Official] Create a publisher
- [Official] Visual Studio Marketplace publisher hub
[!CAUTION] Gotchas
- The publisher ID is PERMANENT. You cannot rename it once an extension is published under it. Pick a name you'll be happy with in 5 years.
- If you sign in with a personal Microsoft account that's also tied to a work tenant, Azure DevOps may default to the work tenant. Switch to the personal directory or your tokens won't work.
Step 3: Mint a Personal Access Token with the right scopes
Estimated time: 15 min
vsce publishes by sending a Personal Access Token (PAT) from Azure DevOps. The PAT MUST be created with Organization scope set to All accessible organizations and the Marketplace → Manage scope. Any other scope set is silently rejected by vsce with an unhelpful error.
Tasks
- https://dev.azure.com/
/_usersSettings/tokens → New Token - Set Organization to All accessible organizations (NOT a single org)
- Set expiration (90 days max for default, you can extend to a year)
- Click 'Show all scopes', tick Marketplace → Manage (NOT just Acquire)
- Copy the PAT — it shows ONCE; lose it and you mint a new one
- Save it in your secrets manager (1Password, Bitwarden, GitHub Actions Secrets)
Pointers
- [Official] Get a Personal Access Token
- [Official] Azure DevOps PAT scopes reference
[!CAUTION] Gotchas
- PAT scope must be Marketplace → MANAGE. Picking only 'Acquire' or 'Publish' fails silently with a 401 from vsce.
- Organization scope must be 'All accessible organizations'. A single-org PAT cannot publish.
- PATs expire. Calendar a renewal at 80 days or your CI publish breaks at the worst time.
Step 4: Configure package.json for the Marketplace
Estimated time: 1-2 hr
The Marketplace listing is generated from package.json + README.md + CHANGELOG.md + the icon file. Required fields: publisher, name, displayName, description, version, engines.vscode, categories, icon (128x128 PNG), repository. Skipping any of these gives a vsce package warning that's actually a fail-on-publish.
Tasks
- Set publisher (the ID you created in step 2)
- Set name (lowercase, kebab-case, unique within publisher)
- Set displayName (human-readable, shown on Marketplace)
- Set description (one sentence, shown in search results)
- Set version using semver (1.0.0 for first release)
- Set engines.vscode to the minimum version your code requires (e.g. ^1.74.0)
- Set categories from the official list (Programming Languages, Linters, Themes, etc.)
- Add icon: a 128x128 PNG, referenced from package.json
- Add repository (REQUIRED for the Marketplace's 'Repository' link)
- Set bugs + homepage URLs
Pointers
- [Official] Extension manifest
- [Official] Categories list
[!CAUTION] Gotchas
- If repository is missing, vsce package warns but still publishes. The Marketplace listing has a broken Repo link until you re-publish with it set.
- engines.vscode must be a real released version. ^1.999.0 fails review for users on stable.
- Icons with alpha channel are fine here, unlike Apple. PNG, 128x128, transparent corners, ship as ./icon.png.
Step 5: Minimise activationEvents (post-1.74 default)
Estimated time: 1-2 hr
VS Code 1.74 changed the default: extensions now activate ONLY on events you explicitly declare. The old onStartupFinished catch-all is allowed but heavily discouraged because it slows VS Code startup for every user. Declare exactly the events your commands / languages / views need.
Tasks
- List every command your extension contributes (in contributes.commands)
- For each command, add onCommand:
to activationEvents (only if the command is the first interaction) - If you contribute a language, use onLanguage:
instead of onStartupFinished - If you contribute a view, use onView:
- If your extension is a theme: NO activation events needed; themes activate lazily
- Run the Extension Development Host with --inspect-extensions to confirm activation timing
Pointers
- [Official] Activation events
- [Official] VS Code 1.74 implicit activation
[!CAUTION] Gotchas
- onStartupFinished forces the extension to load on every VS Code window open, slowing startup for every user. Marketplace surfaces this in the 'Extension Activation Time' chart.
- If your extension uses workspaceContains:
, write a tight glob. workspaceContains:** activates on every workspace and is treated like onStartupFinished. - If you contribute a theme + commands: ONLY declare onCommand events. Themes don't need activation; the user picking your theme triggers it lazily.
Agent prompt for this step
Audit this VS Code extension's source code + package.json and produce the minimum-correct activationEvents list.
Rules:
1. For every command in contributes.commands: include onCommand:<id> only if the user can invoke that command from the Command Palette without already activating the extension some other way (e.g. opening a relevant file).
2. For every language in contributes.languages: include onLanguage:<id>.
3. For every view in contributes.views: include onView:<viewId>.
4. For every webview / authentication / debug provider: use the matching activation event.
5. NEVER include "*" or onStartupFinished unless absolutely necessary, and explain why if you do.
Output the new activationEvents array + a one-line justification per event.
Step 6: Write a README that doubles as the Marketplace listing
Estimated time: 3-5 hr
The Marketplace listing IS your README.md, rendered as GitHub-flavoured markdown. Hero image, animated GIF demo, install instructions, configuration table — all from the README. Spend an afternoon on this; it's the only thing standing between a curious user and an install.
Tasks
- Hero section: extension name + 1-line value prop + animated GIF demo
- Features section: 3-5 bullets, each with a screenshot or GIF
- Configuration section: table of every setting in contributes.configuration
- Commands section: table of every command in contributes.commands
- Requirements section: VS Code version + any external tools (Node, a CLI, etc.)
- Known issues + release notes link to CHANGELOG.md
- License at the bottom + link to GitHub repo
- Run vsce package locally and open the .vsix in VS Code to preview the listing
Pointers
- [Official] Marketplace listing best practices
- [Tool] Animated GIF tools (community) — Record a small GIF of your extension in action; the Marketplace renders GIFs inline.
[!CAUTION] Gotchas
- Relative image paths in README work locally but break on the Marketplace. Use absolute GitHub raw URLs (https://raw.githubusercontent.com/...) for any image referenced in README.md.
- Animated GIFs over 4 MB don't autoplay smoothly on the Marketplace. Compress aggressively (gifski, ezgif).
- The Marketplace renders only the README as the description. Markdown links work, JS doesn't, embedded video doesn't.
Agent prompt for this step
Draft a README for this VS Code extension that doubles as the Marketplace listing.
Read package.json + the source. Output:
1. Hero: extension name + one-sentence value prop. Slot for a GIF demo.
2. Features: 3-5 bullets, each with a placeholder for a screenshot.
3. Configuration: a markdown table of every setting in contributes.configuration with name, type, default, and one-line description.
4. Commands: a markdown table of every command in contributes.commands with id, title, and where to invoke (Command Palette / context menu / shortcut).
5. Requirements: VS Code engine version + external tools.
6. Release Notes: link to CHANGELOG.md.
7. License + repo link.
Tone: developer-to-developer. No "revolutionary", no exclamation marks. Lead with what the extension does, then how to use it.
Step 7: Package and publish with vsce
Estimated time: 30 min for first publish
vsce package builds a .vsix; vsce publish uploads it to the Marketplace. Either bump version manually (vsce publish minor) or bump first then publish (npm version minor && vsce publish). The first publish on a new publisher takes 5-15 minutes to appear in search.
Tasks
- npm i -g @vscode/vsce
- Run vsce package locally to get a .vsix and inspect its contents
- Run vsce publish (or vsce publish minor / patch / major to bump first)
- When prompted, paste the PAT from step 3
- Optional: vsce login
once to cache the PAT for future publishes - Verify the listing at https://marketplace.visualstudio.com/items?itemName=
. - Install the extension in a clean VS Code profile to confirm the install path
Pointers
- [Official] Publishing extensions
- [Official] vsce CLI reference
[!CAUTION] Gotchas
- .vscodeignore is critical. Without it, your .vsix bundles node_modules + .git + tests and uploads 50+ MB. Keep it under 1 MB for theme/snippet packs, under 5 MB for most extensions.
- The first publish takes 5-15 min to appear in search. Don't panic-resubmit.
- If you change the publisher field after first publish, you cannot re-publish under the new publisher. Pick once.
Step 8: Cross-publish to Open VSX for VSCodium + Cursor users
Estimated time: 30 min
The Visual Studio Marketplace's terms of service prohibit non-Microsoft IDE forks (VSCodium, Cursor, Gitpod, Theia) from using it. Open VSX is the open-source mirror those forks pull from. Publishing to both takes one extra command and reaches a much larger fork audience.
Tasks
- Sign up at https://open-vsx.org with your GitHub account
- Create a namespace matching your VS Code Marketplace publisher ID
- Mint an Eclipse Open VSX access token from the User Settings page
- npm i -g ovsx
- Run ovsx publish path/to/your.vsix -p
- Verify the listing at https://open-vsx.org/extension/
/ - Add ovsx publish to your CI workflow next to vsce publish
Pointers
- [Official] Open VSX publishing guide
- [Tool] ovsx CLI
[!CAUTION] Gotchas
- Open VSX namespaces are first-come-first-serve. Claim yours when you create your VS Code publisher, even if you don't publish there day one.
- VSCodium + Cursor pull from Open VSX by default. If you only ship to the VS Code Marketplace, those users see your extension as 'unavailable'.
- If your extension uses a Microsoft-only API (proposed APIs, walkthroughs from internal templates), it may run on VS Code but fail on VSCodium. Test on both.
Step 9: Set up CI to publish on tag + write a CHANGELOG
Estimated time: 1-2 hr
Manually running vsce publish from your laptop is fine for v1. For ongoing maintenance, push the publish step into GitHub Actions: on every git tag like v1.0.1, run npm ci && vsce publish && ovsx publish, with the PAT + Open VSX token in repo Secrets. Add a CHANGELOG.md the Marketplace renders under 'Changelog'.
Tasks
- Create .github/workflows/publish.yml triggered on tag push (v*..)
- Add VSCE_PAT + OVSX_PAT to repo Secrets
- Workflow steps: checkout, npm ci, npm test, vsce publish, ovsx publish
- Adopt CHANGELOG.md (Keep a Changelog format works well: ## [1.0.1] - 2026-04-28 + Added/Changed/Fixed sections)
- Configure auto-changelog or release-please if you don't want to maintain it manually
- Tag v1.0.1 locally + push: git tag v1.0.1 && git push --tags — confirm CI publishes both
Pointers
- [Official] Continuous Integration for extensions
- [Guide] Keep a Changelog format
[!CAUTION] Gotchas
- Secrets in GitHub Actions are not exposed to PRs from forks by default. Your publish workflow must run on push to main or on tag, never on PR.
- vsce publish will refuse if the version in package.json equals an existing version on the Marketplace. Tag-based workflows must npm version
before vsce publish OR fail loudly. - Open VSX has a separate failure mode (rate limit, namespace mismatch). Don't make the workflow fail-fast on ovsx if vsce already succeeded; the Marketplace publish is your hot path.
Hand the template to your agent
Paste the prompt below into your agent's permanent system prompt so the agent reads, writes, and maintains this workspace as you work through the steps.
You are an agent on the "Ship a VS Code extension" playbook workspace at your-org/ship-a-vscode-extension.
Your role: maintain the four surfaces (Steps, Pointers, Brief, Release log) as the user works through the 9-step playbook.
Cadence:
- When the user marks a step Done, append a line to the Brief.
- When a publish runs (vsce publish + ovsx publish), append a row to Release log: version, date, both store URLs, install count delta.
- Mirror new pointers (links) from steps into the Pointers table.
First MCP tool calls:
1. list_surfaces(workspace_slug="ship-a-vscode-extension")
2. list_rows(workspace_slug="ship-a-vscode-extension", surface_slug="steps")
3. get_doc(workspace_slug="ship-a-vscode-extension", surface_slug="brief")
Do NOT modify canonical step titles. Append substeps as new rows.
FAQ
How long does VS Code Marketplace 'review' take?
There is no human review for most extensions. Once vsce publish completes, the listing appears in 5-15 min for new publishers, instantly for existing publishers. Microsoft does occasionally pull extensions for security or policy issues post-publish (e.g. impersonating popular extension names) so don't take 'no review' as 'no consequences'.
Do I need a publisher account before I can develop?
No. You can build and run the extension locally (F5 in VS Code) without ever creating a publisher. The publisher account + Azure DevOps PAT are only required at publish time. Many devs sit on a working .vsix for days before they create the publisher.
Why won't my PAT work?
Three reasons in order of frequency: (1) Organization scope is set to a single org instead of 'All accessible organizations', (2) Marketplace scope is set to Acquire instead of Manage, (3) the PAT belongs to a Microsoft account that's signed into a different Azure DevOps directory than your publisher. Re-mint the PAT with All accessible organizations + Marketplace Manage and try again.
Should I publish to Open VSX too?
Yes, if you want VSCodium / Cursor / Gitpod / Theia users to install you. The VS Code Marketplace ToS bans those forks from using it, so they pull from Open VSX. The publish flow is one extra command (ovsx publish) and reaches an audience that grows every quarter.
Can my AI agents help with the extension?
Yes. Agents are particularly useful for: drafting the README that becomes the Marketplace listing, writing a minimum activationEvents list from a code audit, drafting the CHANGELOG from git log, configuring the GitHub Actions publish workflow. The playbook ships agent prompts inline.