Skip to main content

Finished reading? Get articles like this every Tuesday

Migrate from .sln to .slnx in .NET 10 - Complete Guide to the New Solution Format

Learn how to migrate from .sln to .slnx in .NET 10. Understand the new XML-based solution format, migration methods, CI/CD compatibility, and team adoption strategy.

dotnet

dotnet 10 slnx solution file sln to slnx dotnet migrate visual studio jetbrains rider dotnet cli xml solution format git merge conflicts ci cd github actions msbuild project structure developer tooling dotnet new sln breaking change team adoption

13 min read
2.2K views

Starting with .NET 10, dotnet new sln generates .slnx files by default. Your old .sln files still work, but the writing is on the wall — the .sln format that has haunted .NET developers for over two decades is finally being replaced.

If you’ve ever opened a .sln file and seen a wall of GUIDs, cryptic section headers, and formatting that makes zero sense to humans — you already know why this change was needed. The new .slnx format is XML-based, human-readable, and dramatically easier to work with in Git.

In this article, we’ll cover what .slnx is, why it exists, how to migrate your existing solutions, and everything you need to know about tooling, CI/CD compatibility, and rolling this out across your team.

Let’s get into it.

What is the .slnx File Format?

The .slnx format is a new XML-based solution file format introduced by Microsoft to replace the legacy .sln format. It describes the same thing — which projects belong to a solution, how they’re organized into folders, and build configurations — but in clean, readable XML instead of the proprietary text format that .sln used.

In .NET 10, .slnx is the default format when you create a new solution with the dotnet new sln command. This is an official breaking change documented by Microsoft.

The recommended solution file format for .NET 10 and beyond is .slnx. It’s supported by Visual Studio 2022 (17.13+), JetBrains Rider, VS Code with C# Dev Kit, MSBuild 17.12+, and the .NET CLI (SDK 9.0.200+).

Here’s why developers are excited about it:

  • Human-readable XML — you can open it in any text editor and understand it instantly
  • No more GUIDs — project type GUIDs and solution GUIDs are gone
  • Fewer merge conflicts — the simplified structure means Git diffs are tiny and predictable
  • Consistent with .csproj — the solution file finally speaks the same language as your project files

The Problem with .sln Files

Haven’t we all stared at a .sln file in confusion at some point? Here’s what a typical .sln file looks like for a simple WebAPI project with two class libraries:

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Api", "MyApp.Api\MyApp.Api.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Core", "MyApp.Core\MyApp.Core.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Infrastructure", "MyApp.Infrastructure\MyApp.Infrastructure.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4E5F6A7-B8C9-0123-DEFA-234567890123}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {D4E5F6A7-B8C9-0123-DEFA-234567890123}
{B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {D4E5F6A7-B8C9-0123-DEFA-234567890123}
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {D4E5F6A7-B8C9-0123-DEFA-234567890123}
EndGlobalSection
EndGlobal

That’s 35 lines for three projects. And the real pain points?

  • GUIDs everywhere{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} is a “C# project type GUID.” Good luck remembering that.
  • Merge conflict nightmare — Two developers add a project on different branches? The GUID-based NestedProjects and ProjectConfigurationPlatforms sections create conflicts that are nearly impossible to resolve by hand.
  • Non-standard format — It’s not JSON, not XML, not YAML. It’s a custom format that only Visual Studio truly understands. You can’t validate it with any standard tool.
  • Redundant configuration — Every project gets Debug and Release configuration entries duplicated, even when they all use the same settings.

This format was designed in the early 2000s when Visual Studio was the only way to work with .NET. It was never meant to be edited by humans or merged in Git. The .csproj format was modernized back in .NET Core — it was about time the solution file caught up.

.sln vs .slnx — Side-by-Side Comparison

Here’s that same three-project solution in .slnx format:

<Solution>
<Folder Name="src">
<Project Path="MyApp.Api/MyApp.Api.csproj" />
<Project Path="MyApp.Core/MyApp.Core.csproj" />
<Project Path="MyApp.Infrastructure/MyApp.Infrastructure.csproj" />
</Folder>
</Solution>

That’s 7 lines. Down from 35. Let that sink in.

No GUIDs. No configuration platform mappings. No cryptic section headers. Just a clean XML tree that says: “Here are my projects, and here’s how they’re organized.”

Feature.sln (Legacy).slnx (New)
FormatProprietary textStandard XML
Human-readableBarelyCompletely
GUIDsRequired for every project and folderNone
Lines for 3 projects~35~7
Git merge conflictsFrequent and painfulRare and simple
Configuration platformsExplicitly listed per projectInferred from project files
ToolingVS, MSBuild, .NET CLIVS 2022 17.13+, Rider, VS Code, MSBuild 17.12+, .NET CLI 9.0.200+
Default in .NET 10NoYes
Editable in NotepadTechnically yes, practically noAbsolutely

The .slnx format infers build configurations from the project files themselves, which is why you don’t see Debug|Any CPU mappings. If all your projects use the standard Debug/Release configurations (which 99% of projects do), the .slnx file just works without listing them.

The .NET 10 Breaking Change

In .NET 10, this is now a breaking change. When you run:

Terminal window
dotnet new sln

You get a .slnx file instead of a .sln file. The generated content is minimal:

<Solution>
</Solution>

If you specifically need the old .sln format (for legacy tooling compatibility, for example), you can opt out:

Terminal window
dotnet new sln --format sln

This change was introduced because the .NET SDK added .slnx support in version 9.0.200, and it’s been stable and well-supported across all major .NET tooling since then.

PRO TIP: If you’re starting a new .NET 10 project today, you’ll get .slnx automatically. There’s no reason to use .sln for new projects.

How to Migrate from .sln to .slnx

There are three ways to migrate, depending on your preferred workflow.

This is the fastest and most reliable method. Run this command in the directory containing your .sln file:

Terminal window
dotnet sln MyApp.sln migrate

This generates a new MyApp.slnx file based on your existing .sln. The original .sln file is not deleted — it stays intact so you can validate the migration before removing it.

If you only have one .sln file in the directory, you can simplify:

Terminal window
dotnet sln migrate

The CLI automatically finds the .sln file and creates the .slnx equivalent.

Important: You need .NET SDK 9.0.200 or later for the migrate command. Check your version with dotnet --version.

Method 2: Using Visual Studio

If you prefer the GUI approach:

  1. Open your solution in Visual Studio 2022 (version 17.13 or later)
  2. Go to File > Save Solution As…
  3. In the file type dropdown, change it to Xml Solution File (*.slnx)
  4. Click Save

This creates the .slnx file alongside your existing .sln. You’ll want to remove the old .sln file after validating.

Method 3: Using JetBrains Rider

Rider has supported .slnx since version 2024.3. To migrate:

  1. Open your solution in Rider
  2. Right-click the solution node in the Solution Explorer
  3. Select Save As .slnx

Same deal — the old .sln file is preserved for you to validate.

Migrating a Real Multi-Project Solution

Let’s walk through migrating a real-world WebAPI solution instead of just showing the command. Say you have a typical layered architecture:

MyApp/
├── MyApp.sln
├── src/
│ ├── MyApp.Api/
│ │ └── MyApp.Api.csproj
│ ├── MyApp.Core/
│ │ └── MyApp.Core.csproj
│ └── MyApp.Infrastructure/
│ └── MyApp.Infrastructure.csproj
└── tests/
├── MyApp.UnitTests/
│ └── MyApp.UnitTests.csproj
└── MyApp.IntegrationTests/
└── MyApp.IntegrationTests.csproj

Step 1: Verify Your SDK Version

Terminal window
dotnet --version

Make sure you’re on 9.0.200 or later (ideally .NET 10 SDK).

Step 2: Run the Migration

Terminal window
dotnet sln MyApp.sln migrate

You’ll see output confirming the migration:

The solution file 'MyApp.sln' has been migrated to 'MyApp.slnx'.

Step 3: Inspect the Generated .slnx

Open MyApp.slnx. You should see something like:

<Solution>
<Folder Name="src">
<Project Path="src/MyApp.Api/MyApp.Api.csproj" />
<Project Path="src/MyApp.Core/MyApp.Core.csproj" />
<Project Path="src/MyApp.Infrastructure/MyApp.Infrastructure.csproj" />
</Folder>
<Folder Name="tests">
<Project Path="tests/MyApp.UnitTests/MyApp.UnitTests.csproj" />
<Project Path="tests/MyApp.IntegrationTests/MyApp.IntegrationTests.csproj" />
</Folder>
</Solution>

Notice how solution folders are preserved as <Folder> elements, and nested projects appear as children of their folder. Clean and intuitive.

Step 4: Validate the Build

Terminal window
dotnet build MyApp.slnx

Run your tests too:

Terminal window
dotnet test MyApp.slnx

If both pass, you’re good to proceed.

Step 5: Remove the Old .sln File

Once you’ve confirmed everything works:

Terminal window
rm MyApp.sln

Do NOT keep both files in the same repository. Having both .sln and .slnx causes confusion — the dotnet sln command won’t know which one to use and will prompt every time. Pick one and commit to it.

Solution Folders and Nested Projects in .slnx

One concern developers have is whether .slnx handles solution folders properly. It does — and it’s much cleaner than the old format.

In .sln, solution folders required a special project type GUID ({2150E333-8FDC-42A3-9474-1A3956D46DE8}) and a NestedProjects section to map projects into folders using GUIDs. It looked like this:

Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{SOME-GUID}"
EndProject
...
GlobalSection(NestedProjects) = preSolution
{PROJECT-GUID} = {FOLDER-GUID}
EndGlobalSection

In .slnx, it’s just XML nesting:

<Solution>
<Folder Name="src">
<Project Path="src/MyApp.Api/MyApp.Api.csproj" />
</Folder>
<Folder Name="tests">
<Project Path="tests/MyApp.Tests/MyApp.Tests.csproj" />
</Folder>
</Solution>

You can even nest folders inside folders:

<Solution>
<Folder Name="src">
<Folder Name="services">
<Project Path="src/services/OrderService/OrderService.csproj" />
<Project Path="src/services/PaymentService/PaymentService.csproj" />
</Folder>
<Folder Name="shared">
<Project Path="src/shared/Common/Common.csproj" />
</Folder>
</Folder>
</Solution>

Get the point? The XML structure mirrors the logical organization of your solution. No more cross-referencing GUIDs to figure out which project belongs to which folder.

Tooling Support Matrix

Here’s the current state of .slnx support across the .NET ecosystem:

ToolVersion RequiredStatus
Visual Studio 202217.13+Full support (open, edit, Save As)
JetBrains Rider2024.3+Full support
VS Code + C# Dev KitLatestFull support
.NET CLISDK 9.0.200+Full support (create, migrate, add, remove, list)
MSBuild17.12+Full support
GitHub Actions (dotnet build)Uses SDK versionWorks if SDK ≥ 9.0.200
Azure DevOps (dotnet build)Uses SDK versionWorks if SDK ≥ 9.0.200
Docker (dotnet build in Dockerfile)Uses base image SDKWorks with mcr.microsoft.com/dotnet/sdk:10.0

The key takeaway: if your tooling is reasonably up to date (2024+), .slnx just works. The only scenario where you might hit issues is with very old third-party extensions or custom MSBuild tasks that parse .sln files directly.

CI/CD Pipeline Compatibility

This is the question nobody else answers: does my CI/CD pipeline break when I switch to .slnx?

The short answer: no. If your pipeline uses standard dotnet commands, it works identically.

GitHub Actions

Here’s a typical GitHub Actions workflow. Nothing changes — dotnet build and dotnet test detect the .slnx file automatically:

name: Build and Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --configuration Release

The commands are the same whether you have a .sln or .slnx file. The .NET CLI auto-discovers the solution file in the current directory.

One thing to watch: If your pipeline explicitly references the .sln file by name (e.g., dotnet build MyApp.sln), you’ll need to update that reference to MyApp.slnx.

Docker Builds

Same story with Docker. If your Dockerfile copies and builds the solution:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["MyApp.slnx", "."]
COPY ["src/MyApp.Api/MyApp.Api.csproj", "src/MyApp.Api/"]
COPY ["src/MyApp.Core/MyApp.Core.csproj", "src/MyApp.Core/"]
RUN dotnet restore
COPY . .
RUN dotnet publish "src/MyApp.Api/MyApp.Api.csproj" -c Release -o /app/publish

Just make sure you’re copying the .slnx file instead of .sln. That’s the only change.

Git Merge Conflicts: Before vs After

Everyone says .slnx reduces merge conflicts, but let’s actually prove it. Here’s a real scenario:

Scenario: Two developers on different branches each add a new project to the solution.

With .sln — Merge Conflict

Developer A adds NotificationService:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotificationService", "src\NotificationService\NotificationService.csproj", "{NEW-GUID-A}"
EndProject
...
{NEW-GUID-A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{NEW-GUID-A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{NEW-GUID-A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{NEW-GUID-A}.Release|Any CPU.Build.0 = Release|Any CPU
...
{NEW-GUID-A} = {FOLDER-GUID}

Developer B adds EmailService and gets the same kind of changes. When they merge, Git sees conflicting changes in three different sections of the .sln file — the project declarations, the configuration platforms, and the nested projects section. Resolving this by hand is error-prone and tedious.

With .slnx — Clean Merge

Developer A’s diff:

<Folder Name="src">
<Project Path="src/NotificationService/NotificationService.csproj" />
</Folder>

Developer B’s diff:

<Folder Name="src">
<Project Path="src/EmailService/EmailService.csproj" />
</Folder>

Git can auto-merge these changes because they’re on different lines within the same XML block. No conflict. No manual resolution. Trust me, once you experience this on a team with 5+ developers all working on the same solution, you’ll never go back.

Team Adoption Checklist

Migrating your own solution is easy. Rolling it out across a team takes a bit more planning. Here’s a checklist:

1. Update .gitignore

Add the old .sln file to .gitignore so nobody accidentally adds it back:

# Legacy solution files (migrated to .slnx)
*.sln

Or if you want to be more targeted, ignore only the specific file:

MyApp.sln

2. Verify Everyone’s SDK Version

Run this as a team:

Terminal window
dotnet --version

Everyone needs SDK 9.0.200+ (ideally .NET 10 SDK). If anyone is behind, they won’t be able to open the .slnx file.

3. Create a Single Migration PR

Don’t sneak the migration into a feature PR. Create a dedicated migration PR that:

  • Adds the new .slnx file
  • Removes the old .sln file
  • Updates any CI/CD configs that reference the .sln by name
  • Updates the .gitignore
  • Updates the README.md if it references the .sln file

This makes the change reviewable and easy to revert if something goes wrong.

4. Update IDE-Specific Launch Configs

If your team uses .vscode/launch.json or launchSettings.json files that reference the solution by name, update those too.

5. Communicate the Change

Post in your team’s Slack/Teams channel:

“We’ve migrated from .sln to .slnx. Make sure your SDK is 9.0.200+ and pull the latest. If your IDE doesn’t recognize the file, update to the latest version.”

Simple. No drama.

Troubleshooting Common Issues

”dotnet sln migrate” Command Not Found

You’re on an older SDK version. Check with dotnet --version. You need 9.0.200 or later. Update your SDK:

Terminal window
dotnet --version

If you’re below 9.0.200, download the latest SDK from dotnet.microsoft.com.

”Multiple solution files found” Error

If you have both .sln and .slnx in the same directory, the CLI doesn’t know which one to use. This is expected — remove the old .sln file after validating the migration. If you need to specify which file to use:

Terminal window
dotnet build MyApp.slnx

Solution Folders Are Missing After Migration

This is rare, but if your .sln file had unusual folder nesting, double-check the generated .slnx. The migration tool handles standard layouts well, but exotic folder structures might need a manual tweak. Open the .slnx in any text editor and verify the <Folder> elements look correct.

Third-Party Tools Don’t Recognize .slnx

Some older tools that parse .sln files directly (custom code generators, legacy build scripts) may not support .slnx yet. Options:

  • Check if the tool has an updated version with .slnx support
  • Keep a .sln file generated from .slnx for that specific tool (not ideal, but a temporary workaround)
  • Use the Microsoft.VisualStudio.SolutionPersistence NuGet package if you’re building tools that need to read solution files — it supports both formats

Build Fails in CI After Migration

If your CI pipeline explicitly references MyApp.sln, update it to MyApp.slnx. Also verify the CI environment has .NET SDK 9.0.200+ (or .NET 10 SDK). Most hosted runners (GitHub Actions ubuntu-latest, Azure DevOps Microsoft-hosted agents) are updated regularly, but self-hosted runners may need a manual SDK update.

Key Takeaways

  • .slnx is the default in .NET 10. New solutions created with dotnet new sln now generate .slnx files automatically.
  • Migration takes one command: dotnet sln migrate converts your .sln to .slnx instantly.
  • Git merge conflicts drop dramatically because the XML format is predictable, compact, and doesn’t rely on GUIDs.
  • All major tooling supports it: Visual Studio 2022 (17.13+), Rider (2024.3+), VS Code, MSBuild (17.12+), and the .NET CLI (SDK 9.0.200+).
  • CI/CD pipelines work unchanged — just update any hardcoded .sln file references.

Frequently Asked Questions

What is the .slnx file format in .NET?

The .slnx format is a new XML-based solution file format introduced by Microsoft to replace the legacy .sln format. It describes which projects belong to a solution, how they are organized into folders, and build configurations — but in clean, readable XML instead of the proprietary text format that .sln files used. It is the default format in .NET 10.

How do I migrate from .sln to .slnx?

The fastest way is using the .NET CLI. Run dotnet sln YourSolution.sln migrate in your terminal. This creates a new .slnx file while keeping the original .sln file intact. You can also use Visual Studio 2022 (File > Save Solution As > Xml Solution File) or JetBrains Rider (right-click solution > Save As .slnx). You need .NET SDK 9.0.200 or later.

Is .slnx the default format in .NET 10?

Yes. Starting with .NET 10, the dotnet new sln command generates a .slnx file instead of a .sln file. This is an official breaking change. If you need the old .sln format, you can opt out by running dotnet new sln --format sln.

Does .slnx work with Visual Studio, Rider, and VS Code?

Yes. Visual Studio 2022 version 17.13 and later, JetBrains Rider 2024.3 and later, and VS Code with the C# Dev Kit all fully support .slnx files. You can open, edit, build, and debug solutions using the .slnx format in all three IDEs.

Can I have both .sln and .slnx files in the same repository?

You can, but it is strongly recommended that you do not. Having both files causes confusion because the dotnet sln command will not know which solution file to use and will prompt you every time. It also creates a maintenance burden of keeping both files in sync. Migrate to .slnx and remove the old .sln file.

Does .slnx support solution folders?

Yes. Solution folders in .slnx are represented as Folder XML elements with nested Project elements inside them. This is far cleaner than the old .sln approach which required special project type GUIDs and a separate NestedProjects section. You can also nest folders inside folders using standard XML nesting.

Will .slnx work with my CI/CD pipeline?

Yes. If your CI/CD pipeline uses standard dotnet commands like dotnet build, dotnet test, and dotnet publish, it works identically with .slnx files. The only change needed is updating any hardcoded references from YourSolution.sln to YourSolution.slnx. Make sure your build environment has .NET SDK 9.0.200 or later.

Should I migrate existing projects to .slnx right now?

Yes, if your team is on .NET SDK 9.0.200 or later and your tooling supports it. The migration is low-risk because the original .sln file is preserved and you can validate before removing it. The benefits of fewer merge conflicts, better readability, and alignment with the .NET 10 default make it worth migrating sooner rather than later.

Summary

The .slnx format is one of those changes that seems small but makes a real difference in your daily workflow. No more GUIDs. No more unreadable solution files. No more dreading merge conflicts when two people add projects to the same solution.

With .NET 10 making .slnx the default, now is the time to migrate. It’s a one-command operation (dotnet sln migrate), all major tooling supports it, and your CI/CD pipelines work without changes. The only risk is keeping the old .sln format — you’ll be fighting an uphill battle as the ecosystem moves forward.

If you found this helpful, share it with your team — especially that developer who always gets stuck resolving .sln merge conflicts. They’ll thank you.

Happy Coding :)

What's your Feedback?
Do let me know your thoughts around this article.

8,200+ .NET devs get this every Tuesday

Free weekly newsletter

Stay ahead in .NET

Tutorials Architecture DevOps AI

Once-weekly email. Best insights. No fluff.

Join 8,200+ developers · Delivered every Tuesday