9-step playbook from 'F5 to debug' to 'live in the VS Code Marketplace and Open VSX.' Real publisher gotchas, real activation traps, real agent prompts.
Open in Dock→Developers shipping their first VS Code extension
Publishing to the VS Code Marketplace is mostly a fight with three things: the Azure DevOps publisher account (a non-obvious prerequisite), Personal Access Token scopes (set them wrong and vsce silently refuses), and activation events (the 1.74+ default of activating on every event sunset, you have to declare what you actually need). This playbook walks the 9 gates with the official Microsoft docs, the package.json snippets that work, and agent prompts for the parts agents can do (drafting the README that doubles as the Marketplace listing, configuring CI for vsce publish, picking minimal activation events).
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.
Time2-5 daysDifficultyintermediateForDevelopers shipping their first VS Code extension.
Top to bottom. Each step has tasks, pointers, gotchas.
01 / 09
Scaffold a clean extension with generator-code
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
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.
02 / 09
Create the publisher in Azure DevOps
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
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.
03 / 09
Mint a Personal Access Token with the right scopes
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/<org>/_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)
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.
04 / 09
Configure package.json for the Marketplace
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)
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.
05 / 09
Minimise activationEvents (post-1.74 default)
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:<id> to activationEvents (only if the command is the first interaction)
If you contribute a language, use onLanguage:<id> instead of onStartupFinished
If you contribute a view, use onView:<viewId>
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
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:<glob>, 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.
06 / 09
Write a README that doubles as the Marketplace listing
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
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.
07 / 09
Package and publish with vsce
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 <publisher> once to cache the PAT for future publishes
Verify the listing at https://marketplace.visualstudio.com/items?itemName=<publisher>.<name>
Install the extension in a clean VS Code profile to confirm the install path
.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.
08 / 09
Cross-publish to Open VSX for VSCodium + Cursor users
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 <token>
Verify the listing at https://open-vsx.org/extension/<publisher>/<name>
Add ovsx publish to your CI workflow next to vsce publish
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.
09 / 09
Set up CI to publish on tag + write a CHANGELOG
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*.*.*)
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 <bump> 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
Workspace-wide agent prompt.
Paste this into your agent's permanent system prompt so the agent reads, writes, and maintains the template's surfaces as you work through the steps.
Agent system prompt
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
Common questions on this template.
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.
Open this template as a workspace.
We mint a fresh copy in your org with the steps as table rows, the pointers as a separate table, and the brief as a doc. Bring your agents, start checking off boxes.