If you have ever tried to resolve a merge conflict in a .sln file, you know the pain. GUIDs that mean nothing to a human, duplicated configuration blocks spanning dozens of lines, and a NestedProjects section that maps one opaque identifier to another. The .sln format has been the default for over two decades, and for most of that time it has been a source of friction that teams simply accepted as the cost of doing business.

.NET 10 changes the default. When you run dotnet new sln, you no longer get a .sln file — you get a .slnx. It is an XML-based format that describes the same thing (which projects belong to a solution, how they are organised, and what build configurations exist) but does it in a fraction of the lines, with zero GUIDs, and in a structure that Git actually understands.

The format itself is not brand new. It shipped as an option in .NET SDK 9.0.200 and has been supported in Visual Studio since 17.13. What is new is that it is now the default, and that tooling support across the ecosystem — Rider, VS Code with C# Dev Kit, and the dotnet CLI — is mature enough that you can migrate existing solutions without drama.

What the format looks like

A .sln file for a modest three-project solution with a test project and a solution folder easily runs to 40+ lines. The equivalent .slnx looks like this:

MyApp.slnx
<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.Tests/MyApp.Tests.csproj" />
  </Folder>
</Solution>

That is the entire file. No project type GUIDs, no GlobalSection blocks, no SolutionConfigurationPlatforms mapping every project to every configuration. The format uses sensible defaults — standard Debug|Any CPU and Release|Any CPU configurations are inferred from the project files themselves, so you only need to specify configurations when you deviate from the norm.

Compare that to the .sln equivalent for the same projects:

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", "src\MyApp.Api\MyApp.Api.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
...

The verbose header, the project type GUID (FAE04EC0-... means "C# project" — as if anyone memorises that), the unique project GUID, and then the Global section that duplicates configuration mappings for every single project. It is a format designed for machines to parse, not for humans to maintain.

Solution folders and solution items

Solution folders in .slnx are exactly what you would expect — nested <Folder> elements:

LargeApp.slnx
<Solution>
  <Folder Name="/Solution Items/">
    <File Path=".editorconfig" />
    <File Path="Directory.Build.props" />
    <File Path="Directory.Packages.props" />
    <File Path="global.json" />
  </Folder>
  <Folder Name="/src/">
    <Folder Name="/src/Shared/">
      <Project Path="src/Shared/SharedKernel/SharedKernel.csproj" />
    </Folder>
    <Project Path="src/Web.Api/Web.Api.csproj" />
  </Folder>
</Solution>

Solution items — files like .editorconfig, Directory.Build.props, or docker-compose.yml that you want visible in the solution but are not projects — use the <File> element inside a <Folder>. In the old format, solution items required their own special project type GUID (2150E333-...) and a ProjectSection(SolutionItems) block. The new format is immediately obvious.

Build configuration overrides

For most solutions, you do not need to specify configurations at all. The .slnx format infers Debug and Release from your project files. However, when you need per-project overrides — say, a docker-compose project that should always build, or a project that targets a different platform — you can add explicit configuration entries:

MultiPlatform.slnx
<Solution>
  <Project Path="src/MyApp.Api/MyApp.Api.csproj" />
  <Project Path="docker-compose.dcproj">
    <Build />
  </Project>
  <Configurations>
    <Platform Name="Any CPU" />
    <Platform Name="x64" />
    <Platform Name="x86" />
  </Configurations>
</Solution>

The <Build /> element inside a project entry controls whether that project participates in the build for a given configuration. The <Configurations> element lets you define additional platforms beyond the default. This replaces the sprawling ProjectConfigurationPlatforms section in .sln files, where each project needed an explicit mapping for every configuration-platform combination.

Migrating from .sln

The dotnet CLI provides a one-command migration path:

terminal
dotnet sln MyApp.sln migrate

This generates a .slnx file alongside your existing .sln. Both files describe the same solution, so you can validate the new format without losing the original.

// TIP

Run dotnet build MyApp.slnx and dotnet test MyApp.slnx immediately after migration to verify everything resolves correctly. Only delete the original .sln after your entire team and CI pipeline have confirmed the new file works.

If you are using Visual Studio 2022 (17.13 or later), you can also migrate via File > Save Solution As and selecting the .slnx file type. JetBrains Rider (2024.3+) offers a right-click "Save As .slnx" option in the Solution Explorer.

The "both files exist" trap

There is one sharp edge to be aware of during migration. If both a .sln and a .slnx file exist in the same directory, running dotnet build without specifying a file will fail with an error. The CLI cannot determine which solution to use.

You have two options: either specify the file explicitly (dotnet build MyApp.slnx) or delete the old .sln once you have validated the migration. There is no configuration to set a preference — it is a deliberate choice to force you to clean up rather than silently picking one.

IDE and tooling support

The .slnx format is well-supported across the .NET ecosystem:

Tool Minimum version Notes
.NET CLI SDK 9.0.200 Full support for build, test, add, remove, list
Visual Studio 2022 17.13 Full support
Visual Studio 2026 All versions Full support
JetBrains Rider 2024.3 Full support
VS Code + C# Dev Kit Latest Full support
MSBuild 17.12 Full support via msbuild.exe

// WARNING

If you are using third-party tools that parse solution files directly — custom build scripts, code generators, or older CI plugins — check that they support .slnx before migrating. Some tools have hardcoded .sln parsing and will not recognise the new format.

CI/CD considerations

For most CI/CD pipelines, the migration is straightforward. If you are using standard dotnet commands, the only change is updating any hardcoded file references from MyApp.sln to MyApp.slnx:

.github/workflows/build.yml
steps:
  - uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '10.0.x'

  - run: dotnet build MyApp.slnx --configuration Release
  - run: dotnet test MyApp.slnx --configuration Release --no-build

If you are using Docker multi-stage builds, update your COPY instruction:

Dockerfile
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 MyApp.slnx

The key requirement is that your build environment runs .NET SDK 9.0.200 or later. If you are already on .NET 10 (and you should be, given it is the current release), this is not a concern.

Why Git merges actually work now

This is the real reason to care about .slnx. Consider two developers working on the same solution:

With a .sln file, this creates conflicts in at least three sections: the project declarations (different GUIDs on adjacent lines), the SolutionConfigurationPlatforms section (configuration mappings for each new project), and the NestedProjects section (folder mappings using GUIDs). Manual resolution requires understanding the GUID relationships, which is error-prone and tedious.

With a .slnx file, each developer adds a single <Project> line inside the <Folder> element. Git auto-merges this cleanly because the changes are on separate lines within a well-structured XML block. No manual intervention required.

This alone justifies the migration for any team larger than one developer.

Opting out

If you need to generate a .sln file instead of .slnx (perhaps for compatibility with a tool that does not yet support the new format), you can pass the --format flag:

terminal
dotnet new sln --format sln

This is an escape hatch, not a recommendation. The .slnx format is the future of .NET solution files, and the tooling ecosystem has had over a year to catch up since the format first shipped in SDK 9.0.200.

Common pitfalls

Forgetting to update CI file references. The migration generates a new file with a different extension. Your dotnet build command will fail if it references MyApp.sln and that file no longer exists. Search your entire CI configuration for .sln references.

Running both files side by side in production. Keep the .sln around during validation, but remove it promptly. Having both files in the repository creates confusion about which is authoritative, and dotnet build will refuse to work without an explicit file argument.

Assuming all tools support .slnx. Most mainstream tools do, but niche utilities — code generation tools, custom MSBuild orchestrators, or older versions of CI plugins — might parse .sln files directly. Test your full pipeline before deleting the original.

Not updating .gitignore. If your team has been ignoring .sln files (unlikely but possible) or has specific merge strategies configured for them, update those rules to cover .slnx as well.

Expecting nested folder syntax to match the file system. Solution folder names in .slnx are display names, not file paths. A <Folder Name="/src/"> does not need to correspond to a physical src/ directory, though by convention it usually does.

Summary