Visual Studio Integration Guide

Technology: visual-studio · Category: tooling · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/visual-studio

Insight:

Visual Studio has two extensibility worlds. The modern Microsoft.VisualStudio.Extensibility SDK runs your extension out-of-process (async, hot-reload, no VS restart) and is where new editor work should start. The legacy VSSDK + MEF model (IClassifier, adornments, taggers) runs in-process via COM and still owns the deepest editor hooks. Pick the SDK first; drop to VSSDK only for a hook the SDK doesn't yet expose. Extensions ship as NuGet/VSIX, never npm.

██╗   ██╗██╗███████╗██╗   ██╗ █████╗ ██╗         ███████╗████████╗██╗   ██╗██████╗ ██╗ ██████╗
██║   ██║██║██╔════╝██║   ██║██╔══██╗██║         ██╔════╝╚══██╔══╝██║   ██║██╔══██╗██║██╔═══██╗
██║   ██║██║███████╗██║   ██║███████║██║         ███████╗   ██║   ██║   ██║██║  ██║██║██║   ██║
╚██╗ ██╔╝██║╚════██║██║   ██║██╔══██║██║         ╚════██║   ██║   ██║   ██║██║  ██║██║██║   ██║
 ╚████╔╝ ██║███████║╚██████╔╝██║  ██║███████╗    ███████║   ██║   ╚██████╔╝██████╔╝██║╚██████╔╝
  ╚═══╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝    ╚══════╝   ╚═╝    ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝

Visual Studio Integration Guide

Focus: Extending the Visual Studio IDE's editor and tooling — building VSIX extensions with the modern out-of-process VisualStudio.Extensibility SDK and the classic VSSDK + MEF editor model (classifiers, adornments, taggers), and driving builds/tests from Claude Code via MSBuild and the dotnet/devenv CLIs.

Overview

Visual Studio is Microsoft's full Windows IDE for .NET, C++, web, and cross-platform mobile development. Unlike VS Code (a separate, lighter editor with a JavaScript extension model), Visual Studio extensions are .NET assemblies packaged as a VSIX and published to the Visual Studio Marketplace.

Versions & channels (2026-08). Visual Studio 2026 (version 18.x) is now the current major release. It renames the update channels: Stable replaces the old Current channel and Insiders replaces Preview — you can run both side by side. Visual Studio 2022 (17.x) reached its final feature minor at 17.14, which stays on the Current channel and is supported for the rest of its 10-year lifecycle to January 2032. Both major versions build the same extensions; the VisualStudio.Extensibility SDK is still versioned 17.14.x on NuGet (see the packages table) and targets net8.0, so it installs into VS 2022 17.9+ and VS 2026. Check Help > About or devenv /version before scaffolding.

Here's the big picture to keep you oriented — pick your model, scaffold, then let the build tools package the .vsix:

flowchart TD
    A["Need to extend<br/>Visual Studio"] --> Q1{"Hook exposed by<br/>the new SDK?"}
    Q1 -->|"yes"| B["VisualStudio.Extensibility SDK<br/>out-of-process · async · hot-reload"]
    Q1 -->|"no"| C["VSSDK + MEF<br/>in-process COM · deep editor hooks"]
    B --> D["Scaffold contribution classes"]
    C --> D
    D --> E["dotnet build / msbuild"]
    E --> F["Packaged .vsix"]
    F --> G["VS Marketplace or<br/>VSIXInstaller"]

There are two extensibility models, and knowing which you're in saves hours:

Model Process API style Use it when
VisualStudio.Extensibility SDK (new) Out-of-process Async, [VisualStudioContribution], hot-reload New commands, editor listeners, tool windows, LSP — start here
VSSDK + MEF (classic) In-process (COM) [Export]/[Import], requires VS restart Deep editor hooks: IClassifier, adornments, taggers, IntelliSense not yet in the new SDK

Claude Code's role here is codegen + build orchestration: scaffold the extension classes that match the fetched API, then drive dotnet build / msbuild / dotnet test to compile, package, and validate the .vsix.

Official Documentation

Resource URL
Extensibility overview https://learn.microsoft.com/en-us/visualstudio/extensibility/
VisualStudio.Extensibility (new SDK) https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/
Language service & editor extension points https://learn.microsoft.com/en-us/visualstudio/extensibility/language-service-and-editor-extension-points
VSExtensibility SDK (GitHub + samples) https://github.com/microsoft/VSExtensibility
Visual Studio IDE docs https://learn.microsoft.com/en-us/visualstudio/ide/
GitHub Copilot in Visual Studio (get started) https://learn.microsoft.com/en-us/visualstudio/ide/visual-studio-github-copilot-get-started

NuGet Packages (tracked manually — not npm/pypi)

The freshness checker only follows npm and PyPI, so the frontmatter packages list is empty. Track these by hand:

Package Purpose Version at review
Microsoft.VisualStudio.Extensibility.Sdk Core out-of-process SDK 17.14.40608
Microsoft.VisualStudio.Extensibility.Build MSBuild targets that pack the .vsix 17.14.40608
Microsoft.VisualStudio.SDK (classic) Meta-package for VSSDK/MEF editor APIs matches your VS version (e.g. 17.x)
Microsoft.VSSDK.BuildTools (classic, CI) Restores VsSDK.targets so classic VSIX builds without a full VS install 18.9.x (latest on NuGet)

Latest versions: https://www.nuget.org/packages/Microsoft.VisualStudio.Extensibility.Sdk

Note — the SDK still tracks 17.14, not the IDE's 18.x. Even though Visual Studio 2026 ships as version 18.x, the modern VisualStudio.Extensibility SDK's latest stable NuGet release is 17.14.40608 (the Microsoft.VSSDK.BuildTools classic-CI package has moved to 18.9.x). Pin 17.14.40608 for the SDK — it is current, not stale — and re-check the NuGet link before a release.


Prerequisites

Visual Studio 2022 17.9+  (or Visual Studio 2026)  with the "Visual Studio extension development" workload
.NET 8 SDK

The SDK's docs still label it VisualStudio.Extensibility (Preview) and the API surface keeps a small set of experimental members — pin the SDK version and expect occasional breaking changes between minors. It remains Microsoft's "start here" model for new extensions.

Project file

The SDK targets net8.0-windows and pulls two NuGet packages. The .Build package wires the VSIX packaging into dotnet build automatically — no source.extension.vsixmanifest hand-editing:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>12</LangVersion>
    <NeutralLanguage>en-US</NeutralLanguage>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk"   Version="17.14.40608" PrivateAssets="all" />
    <PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
  </ItemGroup>
</Project>

Extension entry point

Every extension has one class deriving from Extension, decorated with [VisualStudioContribution]. This is your manifest-in-code:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Extensibility;

[VisualStudioContribution]
public class InsertGuidExtension : Extension
{
    public override ExtensionConfiguration ExtensionConfiguration => new()
    {
        Metadata = new(
            id: "InsertGuid.c5481000-68da-416d-b337-32122a638980",
            version: this.ExtensionAssemblyVersion,
            publisherName: "codeAmani",
            displayName: "Insert Guid Sample Extension",
            description: "Inserts a GUID at the caret in the active document."),
    };

    protected override void InitializeServices(IServiceCollection serviceCollection)
    {
        base.InitializeServices(serviceCollection);
        // Register your own services for DI here.
    }
}

A command

Commands are Command subclasses, also marked [VisualStudioContribution]. They run async against the out-of-process Extensibility object:

[VisualStudioContribution]
public class InsertGuidCommand : Command
{
    public override CommandConfiguration CommandConfiguration => new("%InsertGuid.DisplayName%")
    {
        Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
        Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
    };

    public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken ct)
    {
        var textView = await context.GetActiveTextViewAsync(ct);
        if (textView is null) return;

        await this.Extensibility.Editor().EditAsync(batch =>
        {
            var doc = textView.Document.AsEditable(batch);
            doc.Replace(textView.Selection.Extent, Guid.NewGuid().ToString());
        }, ct);
    }
}

Build & run

# From the extension project dir — the .Build package produces the .vsix
dotnet build -c Release

# F5 in Visual Studio launches the VS Experimental Instance with the extension
# hot-loaded (no restart). From CLI, install the packaged VSIX:
"%VsInstallDir%\Common7\IDE\VSIXInstaller.exe" bin\Release\MyExtension.vsix

Setup — Classic VSSDK + MEF (deep editor hooks)

When you need an editor hook the new SDK doesn't expose yet — syntax classification, adornments, taggers — use the in-process MEF model. Create a VSIX Project (C# › Extensibility), then add an Editor Classifier item template.

MEF is the wiring: you [Export] a provider and Visual Studio [Import]s it. The editor discovers your component by the exported interface + ContentType.

Middle path — a VSSDK-compatible SDK extension. You no longer have to choose one world wholesale. The VisualStudio.Extensibility Extension with VSSDK Compatibility project template lets a modern SDK extension host classic VSSDK/MEF parts in-process: set <VssdkCompatibleExtension>true</VssdkCompatibleExtension>, mark the Extension with RequiresInProcessHosting = true, and keep the source.extension.vsixmanifest with ExtensionType = VSSDK+VisualStudio.Extensibility. For VS 2022 this variant targets .NET Framework 4.7.2 (not net8.0), because in-process code runs inside devenv.exe. Reach for it when you want the SDK's authoring model but still need a deep MEF hook in the same VSIX. See Using the SDK and VSSDK together.

A classifier (colors text)

[Export(typeof(IClassifierProvider))]
[ContentType("text")]
internal class EditorClassifierProvider : IClassifierProvider
{
    [Import] internal IClassificationTypeRegistryService ClassificationRegistry { get; set; }

    public IClassifier GetClassifier(ITextBuffer buffer) =>
        buffer.Properties.GetOrCreateSingletonProperty(
            () => new EditorClassifier(ClassificationRegistry));
}

internal class EditorClassifier : IClassifier
{
    private readonly IClassificationType _type;
    internal EditorClassifier(IClassificationTypeRegistryService registry) =>
        _type = registry.GetClassificationType("EditorClassifier");

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span) =>
        new List<ClassificationSpan>
        {
            new(new SnapshotSpan(span.Snapshot, span.Span), _type),
        };

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
}

The format definition (how the classification looks)

[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "EditorClassifier")]
[Name("EditorClassifier")]
[UserVisible(true)]
internal sealed class EditorClassifierFormat : ClassificationFormatDefinition
{
    public EditorClassifierFormat()
    {
        DisplayName = "EditorClassifier";
        BackgroundColor = Colors.BlueViolet;
        ForegroundColor = Colors.White;
    }
}

Editor extension points at a glance

Hook Export Reach for it when
Classifier IClassifierProvider Color/categorize spans of text
Tagger ITaggerProvider Attach typed tags (errors, outlining, highlights) to spans
Adornment AdornmentLayerDefinition + IWpfTextViewCreationListener Draw WPF visuals over/under text
Completion IAsyncCompletionSourceProvider Custom IntelliSense
Margin IWpfTextViewMarginProvider Add a gutter/margin UI strip

MEF components are in-process and lazy — Visual Studio only constructs them when a matching ContentType view opens. Keep constructors cheap; do real work on first use.


Driving Visual Studio from Claude Code

Claude Code runs on the CLI, so orchestrate the build tools, not the GUI. Always pass arguments as arrays (execFileSync) — never interpolate paths into a shell string.

You're the codegen-plus-orchestration layer here — here's how a run flows end to end:

sequenceDiagram
    participant CC as "Claude Code"
    participant FS as "Project files"
    participant BT as "dotnet / msbuild"
    participant VS as "Visual Studio"
    CC->>FS: "Scaffold extension classes"
    CC->>BT: "execFileSync with array args"
    BT->>BT: "Compile and pack .vsix"
    BT-->>CC: "Build result · .vsix path"
    CC->>BT: "dotnet test"
    BT-->>CC: "results.trx"
    CC->>VS: "Install or F5 Experimental Instance"
# Build a solution (prefer the dotnet CLI for SDK-style projects)
dotnet build MyExtension.sln -c Release

# MSBuild for classic VSSDK projects that aren't SDK-style
msbuild MyExtension.sln /p:Configuration=Release /p:DeployExtension=false

# Run tests
dotnet test --logger "trx;LogFileName=results.trx"

# Locate the active VS install (avoids hard-coded paths)
vswhere -latest -property installationPath
// scripts/build-vsix.js — safe argument passing
import { execFileSync } from "node:child_process";

execFileSync("dotnet", ["build", "MyExtension.sln", "-c", "Release"], {
  stdio: "inherit",
});

CI — build the VSIX

The tables above point to "CI build of the .vsix" but never show the workflow. Here it is. Two facts shape it:

The runner must locate msbuild first. The canonical action is microsoft/setup-msbuild, which runs vswhere and prepends the discovered MSBuild to PATH.

Pin the runner image — windows-latest moved to VS 2026. As of 2026 the windows-latest label maps to Windows Server 2025 with Visual Studio 2026 (18.x); Visual Studio 2022 is no longer on that image. So vs-version: '17.0' on windows-latest no longer resolves. Pick one deliberately: pin runs-on: windows-2022 to keep the VS 2022 (17.x) toolset (shown below — matches the 17.14 SDK line), or stay on windows-latest and bump the pin to vs-version: '18.0' for the VS 2026 toolset. Don't leave a 17.0 pin on windows-latest.

flowchart TD
    A["push or PR"] --> B["windows-2022 runner<br/>(pinned — latest now ships VS 2026)"]
    B --> C["actions/checkout"]
    C --> D["microsoft/setup-msbuild<br/>adds msbuild to PATH"]
    D --> E["nuget/msbuild restore"]
    E --> F["msbuild · Release<br/>DeployExtension false"]
    F --> G["actions/upload-artifact<br/>the .vsix"]

Workflow — .github/workflows/build-vsix.yml

name: Build VSIX

on:
  push:
    branches: [master]
  pull_request:

jobs:
  build:
    # Pinned: `windows-latest` now = Windows Server 2025 + Visual Studio 2026 (18.x).
    # `windows-2022` keeps the VS 2022 (17.x) toolset that matches `vs-version: '17.0'`.
    runs-on: windows-2022

    steps:
      - uses: actions/checkout@v4

      # Discovers MSBuild via vswhere and adds it to PATH.
      # vs-version pins the toolset (17.0 = VS 2022; use 18.0 on a VS 2026 runner).
      - name: Add MSBuild to PATH
        uses: microsoft/setup-msbuild@v3
        with:
          vs-version: '17.0'

      # Restore NuGet packages — msbuild -t:Restore avoids a separate nuget.exe.
      - name: Restore
        run: msbuild MyExtension.sln -t:Restore -p:Configuration=Release

      # Build and pack. DeployExtension=false: no local VS to install into.
      - name: Build VSIX
        run: >-
          msbuild MyExtension.sln
          -p:Configuration=Release
          -p:DeployExtension=false
          -m

      - name: Upload VSIX
        uses: actions/upload-artifact@v4
        with:
          name: MyExtension-vsix
          path: '**/bin/Release/**/*.vsix'
          if-no-files-found: error

Gotcha — the VS extension build tooling may be missing. The GitHub-hosted Windows images ship a full Visual Studio install (VS 2022 on windows-2022, VS 2026 on windows-latest/Server 2025) plus the .NET workloads, but a classic VSSDK build also needs the Visual Studio extension development workload (the Microsoft.VsSDK.targets that pack the .vsix). The full VS install usually includes it, but if you hit error MSB4019: The imported project "...Microsoft.VsSDK.targets" was not found, the SDK targets aren't on the runner. Fixes, cheapest first: reference the Microsoft.VSSDK.BuildTools NuGet package so the targets restore with the project (preferred — keeps the build self-contained); or, for a container/self-hosted runner, add the component via the VS Installer (--add Microsoft.VisualStudio.Workload.VisualStudioExtension). The modern VisualStudio.Extensibility SDK sidesteps this entirely — its .Build package brings the packaging targets in as a normal PackageReference.


GitHub Copilot in Visual Studio

Copilot is now a first-class part of the IDE, not an add-on. In Visual Studio 2026 it is built in; in Visual Studio 2022 it ships as the GitHub Copilot optional component in the .NET desktop / other workloads, and agent mode requires 17.14+. Sign in once under Tools > Options > GitHub > Accounts with a GitHub account that has Copilot access (Copilot is a separate GitHub subscription, paid or free).

Surface What it does
Completions + next edit suggestions Inline gray-text completions as you type, plus predicted edits to existing code. IntelliSense still takes Tab by default.
Ask (Copilot Chat) Q&A and code examples with no edits applied unless you choose Apply.
Plan agent Read-only exploration that drafts a reviewable implementation plan (saved as markdown under .copilot/plans/) before any edits — hand it off to agent mode to execute.
Agent mode Multi-step edits across solution files, iterating on build errors and running tools. The evolution of Copilot Edits. Required to use MCP servers.
MCP servers Agent mode can call Model Context Protocol tools via the tools icon — configure servers and pick which tools Copilot may use.

Model picker — including Claude

Copilot Chat has a model picker at the bottom of the chat window. With 17.14 the default model is GPT-4.1 (previously GPT-4o), but the picker exposes an expanded set — Claude Sonnet 4, Claude Opus 4, GPT-5 / GPT-5 mini, Claude Sonnet 3.5, Claude 3.7 (thinking / non-thinking), o3-mini, and Gemini 2.x. Model availability depends on your Copilot plan; for Business/Enterprise an admin enables the models.

Bring your own model (BYOM): in the model picker you can add an API key from Anthropic, OpenAI, or Google and use your own model — but only in the Copilot Chat experience (not completions), and not for Copilot Business/Enterprise seats. Custom-model output comes straight from the provider and may bypass Copilot's responsible-AI filtering.

Safety — agent-mode terminal commands. Agent mode can only touch files in the open solution, but any terminal command it proposes runs with the permissions of the Visual Studio process — it is not sandboxed. Review proposed commands before letting them run.

codeAmani angle


Common Use Cases

Use Case Approach
Insert/transform text at caret New SDK Command + Editor().EditAsync
Syntax highlighting for a custom language VSSDK IClassifierProvider + ClassificationFormatDefinition
Squiggles / error tags VSSDK ITaggerProvider<IErrorTag>
Inline visuals (CodeLens-like) VSSDK adornment layer + IWpfTextViewCreationListener
Custom IntelliSense IAsyncCompletionSourceProvider
Language server integration New SDK LSP extension contribution
AI codegen / multi-step edits in the IDE GitHub Copilot agent mode (pick a Claude model) or Claude Code CLI in the terminal
CI build of the .vsix dotnet build / msbuild in GitHub Actions (Windows runner)

Troubleshooting

Issue Fix
Extension not loading Check the Experimental Instance: devenv /rootSuffix Exp; reset with /resetSettings
MEF component never constructed ContentType mismatch — verify the [ContentType] matches the open file's type
.vsix not produced Ensure Microsoft.VisualStudio.Extensibility.Build (or the VSSDK targets) is referenced
Stale extension after rebuild Clear the Exp cache under %LocalAppData%\Microsoft\VisualStudio\<version>_*Exp\Extensions — 17.0_*Exp for VS 2022, 18.0_*Exp for VS 2026
msbuild not found in CI Use the microsoft/setup-msbuild action or build with dotnet for SDK-style projects
No Ask / Plan / Agent options in Copilot Chat You're below VS 17.14 (check Help > About), or Enable Agent mode is off under Tools > Options > GitHub > Copilot > Copilot Chat

codeAmani notes

Official docs: