{"id":256621,"date":"2026-06-29T07:00:25","date_gmt":"2026-06-29T14:00:25","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/visualstudio\/?p=256621"},"modified":"2026-06-26T12:16:57","modified_gmt":"2026-06-26T19:16:57","slug":"automating-your-visual-studio-extension-builds-with-github-actions","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/visualstudio\/automating-your-visual-studio-extension-builds-with-github-actions\/","title":{"rendered":"Automating your Visual Studio extension builds with GitHub Actions"},"content":{"rendered":"<p>If you\u2019re building and maintaining Visual Studio extensions, you\u2019ve probably ended up with some sort of build and publishing workflow &#8211; whether it\u2019s manual, scripted, or stitched together over time.<\/p>\n<p>This post is for extension authors who want a simple, repeatable way to build, version, and publish their VSIX files using GitHub Actions.<\/p>\n<p>I\u2019m going to show how I do this across my own extensions.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/06\/github-action-publish.webp\"><img decoding=\"async\" class=\"alignnone size-full wp-image-256632\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/06\/github-action-publish.webp\" alt=\"github action publish image\" width=\"583\" height=\"425\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/06\/github-action-publish.webp 583w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2026\/06\/github-action-publish-300x219.webp 300w\" sizes=\"(max-width: 583px) 100vw, 583px\" \/><\/a><\/p>\n<p>I\u2019ve been using this approach for a long time, and over time I pulled the most repetitive pieces into a few small reusable actions, so I don\u2019t have to keep rewriting the same logic in every repo.<\/p>\n<p>Those are:<\/p>\n<ul>\n<li><strong>vsix-version-stamp<\/strong> \u2013 keeps your versioning in sync<\/li>\n<li><strong>publish-vsixgallery<\/strong> \u2013 publishes CI builds for testing<\/li>\n<li><strong>publish-marketplace<\/strong> \u2013 publishes to the Visual Studio Marketplace<\/li>\n<\/ul>\n<p>You can use them independently or together, but I tend to use all three.<\/p>\n<p>If you want to see this wired up in a real repo, take a look at <a href=\"https:\/\/github.com\/madskristensen\/startscreen\">Start Screen<\/a>.<\/p>\n<p><strong>A real workflow<\/strong><\/p>\n<p>Here\u2019s a simplified setup very similar to what I use across my extensions today:<\/p>\n<pre class=\"prettyprint language-yaml\"><code class=\"language-yaml\">name: Build\r\non:\r\n  push:\r\n    branches: [ main ]\r\n  pull_request:\r\n    branches: [ main ]\r\n\r\njobs:\r\n  build:\r\n    runs-on: windows-latest\r\n\r\n    env:\r\n      Configuration: Release\r\n      VsixManifestPath: src\\source.extension.vsixmanifest\r\n      VsixSourcePath: src\\source.extension.cs\r\n\r\n    steps:\r\n      - uses: actions\/checkout@v6\r\n\r\n      - name: Setup MSBuild\r\n        uses: microsoft\/setup-msbuild@v3\r\n\r\n      - name: Restore\r\n        run: msbuild \/t:Restore\r\n\r\n      - name: Version stamp\r\n        uses: madskristensen\/vsix-version-stamp@v2\r\n        with:\r\n          manifest-file: ${{ env.VsixManifestPath }}\r\n          vsix-token-source-file: ${{ env.VsixSourcePath }}\r\n\r\n      - name: Build\r\n        run: msbuild \/p:Configuration=$(Configuration)\r\n\r\n      - name: Publish to VSIX Gallery\r\n        uses: madskristensen\/publish-vsixgallery@v1\r\n        with:\r\n          vsix-file: '**\/*.vsix'\r\n\r\n      - name: Publish to Marketplace\r\n        uses: madskristensen\/publish-marketplace@v2\r\n        with:\r\n          extension-file: '**\/*.vsix'\r\n          publish-manifest-file: vs-publish.json\r\n          personal-access-code: ${{ secrets.VS_MARKETPLACE_TOKEN }}<\/code><\/pre>\n<p>This is essentially the full pipeline &#8211; version, build, package, and publish.<\/p>\n<p>From here, you can tweak when publishing happens (for example, only on releases), but the core setup tends to stay the same.<\/p>\n<h2><strong> Keeping your version in sync<\/strong><\/h2>\n<p>Versioning is one of those things that\u2019s easy to get wrong.<\/p>\n<p>The <strong>vsix-version-stamp<\/strong> action updates your version during the build, so you don\u2019t have to think about it.<\/p>\n<p>It works especially well together with the\n<a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=MadsKristensen.VsixSynchronizer64\">VSIX Synchronizer extension<\/a>, which generates a .cs file from your .vsixmanifest.<\/p>\n<p>That gives you:<\/p>\n<ul>\n<li>A single source of truth<\/li>\n<li>Version available in code<\/li>\n<li>No manual edits before publishing<\/li>\n<\/ul>\n<p>It\u2019s completely optional, but once you start using it, it tends to stick.<\/p>\n<h2><strong> Publishing to the Visual Studio Marketplace<\/strong><\/h2>\n<p>Once you have a VSIX, publishing it to the Marketplace is straightforward.<\/p>\n<p>You only need a single secret:<\/p>\n<ul>\n<li>VS_MARKETPLACE_TOKEN<\/li>\n<\/ul>\n<pre class=\"prettyprint language-yaml\"><code class=\"language-yaml\">- name: Publish to Marketplace\r\n  uses: madskristensen\/publish-marketplace@v2\r\n  with:\r\n    extension-file: '**\/*.vsix'\r\n    publish-manifest-file: vs-publish.json\r\n    personal-access-code: ${{ secrets.VS_MARKETPLACE_TOKEN }}<\/code><\/pre>\n<p>That\u2019s it.<\/p>\n<p>The VSIX contains the extension metadata, and the publish manifest fills in the rest.<\/p>\n<h2><strong> Publishing to a VSIX Gallery (for CI builds and testing)<\/strong><\/h2>\n<p>The <strong>publish-vsixgallery<\/strong> action serves a different purpose.<\/p>\n<p>It\u2019s for quickly sharing builds.<\/p>\n<p>I primarily use it when I want someone to try out a fix or validate a change before it goes to the Marketplace.<\/p>\n<pre class=\"prettyprint language-yaml\"><code class=\"language-yaml\">- name: Publish to VSIX Gallery\r\n  uses: madskristensen\/publish-vsixgallery@v1\r\n  with:\r\n    vsix-file: '**\/*.vsix'<\/code><\/pre>\n<p>That\u2019s what VSIX galleries are great for &#8211; fast, lightweight distribution without the overhead of a full release.<\/p>\n<p><strong>Works with your own gallery too<\/strong><\/p>\n<p>VSIX Gallery is open source, so you can host your own instance if you want.<\/p>\n<p>The GitHub Action supports a configurable gallery-url, so it\u2019s not tied to a specific hosted gallery.<\/p>\n<pre class=\"prettyprint language-yaml\"><code class=\"language-yaml\">- name: Publish to VSIX Gallery\r\n  uses: madskristensen\/publish-vsixgallery@v1\r\n  with:\r\n    vsix-file: '**\/*.vsix'\r\n    gallery-url: 'https:\/\/your-gallery.example.com'<\/code><\/pre>\n<p>That lets you use the same workflow whether you\u2019re targeting a public gallery or something you host yourself.<\/p>\n<h2><strong>Mixing and matching<\/strong><\/h2>\n<p>You don\u2019t have to use all three actions.<\/p>\n<p>Some common setups:<\/p>\n<p><strong>Minimal<\/strong><\/p>\n<ul>\n<li>Build + Marketplace publish<\/li>\n<\/ul>\n<p><strong>CI-focused<\/strong><\/p>\n<ul>\n<li>Build + VSIX Gallery publish<\/li>\n<\/ul>\n<p><strong>Full pipeline<\/strong><\/p>\n<ul>\n<li>Version stamping + build + gallery + Marketplace<\/li>\n<\/ul>\n<p>Use what fits your workflow.<\/p>\n<h2><strong>When to use what<\/strong><\/h2>\n<ul>\n<li><strong>VSIX Gallery<\/strong>\nUse this for testing, sharing builds, and quick validation<\/li>\n<li><strong>Visual Studio Marketplace<\/strong>\nUse this for official releases<\/li>\n<\/ul>\n<p>Most extensions benefit from using both:<\/p>\n<ul>\n<li>CI builds go to a gallery<\/li>\n<li>Stable builds go to the Marketplace<\/li>\n<\/ul>\n<h2><strong>Wrap-up<\/strong><\/h2>\n<p>This is the setup I use across my extensions.<\/p>\n<p>It keeps things predictable, makes it easy to share builds, and removes most of the repetitive steps from the release process.<\/p>\n<p>You don\u2019t need to adopt all of it. Start with the parts that make sense for your workflow and build from there.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019re building and maintaining Visual Studio extensions, you\u2019ve probably ended up with some sort of build and publishing workflow &#8211; whether it\u2019s manual, scripted, or stitched together over time. This post is for extension authors who want a simple, repeatable way to build, version, and publish their VSIX files using GitHub Actions. I\u2019m going [&hellip;]<\/p>\n","protected":false},"author":642,"featured_media":256632,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[155],"tags":[294,4381],"class_list":["post-256621","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-visual-studio","tag-extensions","tag-github"],"acf":[],"blog_post_summary":"<p>If you\u2019re building and maintaining Visual Studio extensions, you\u2019ve probably ended up with some sort of build and publishing workflow &#8211; whether it\u2019s manual, scripted, or stitched together over time. This post is for extension authors who want a simple, repeatable way to build, version, and publish their VSIX files using GitHub Actions. I\u2019m going [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/256621","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/users\/642"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/comments?post=256621"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/256621\/revisions"}],"predecessor-version":[{"id":256643,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/256621\/revisions\/256643"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media\/256632"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media?parent=256621"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/categories?post=256621"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/tags?post=256621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}