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:
<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:
<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:
<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:
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:
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:
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:
- Developer A adds a new project
Notifications.csprojto the/src/folder - Developer B adds a new project
Analytics.csprojto the same/src/folder
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:
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
.slnxis the default solution format in .NET 10, replacing the decades-old.slnformat- The format is XML-based, dramatically smaller, and eliminates project type GUIDs entirely
- Standard build configurations are inferred from project files — you only specify overrides
- Migration is a single
dotnet sln migratecommand, with full support across Visual Studio, Rider, VS Code, and the CLI - Git merge conflicts on solution files become a thing of the past for most common scenarios
- Tooling support requires .NET SDK 9.0.200 or later, which any .NET 10 project already satisfies
- The
--format slnescape hatch exists for legacy tool compatibility, but the ecosystem has broadly adopted.slnx