In this article we are going to learn how to set up Scalar as our API documentation UI in .NET 10 and why it's a better choice than Swagger for most modern .NET projects.
If we've started a new .NET 10 Web API project recently, we probably noticed something. The /swagger URL is gone. No Swagger UI by default. Microsoft removed Swashbuckle from the default Web API template starting with .NET 9, and .NET 10 doubles down on that decision with a fully matured native OpenAPI implementation.
This isn't Microsoft killing API documentation. It's them replacing a third-party dependency with something better — their own Microsoft.AspNetCore.OpenApi package for document generation, and Scalar as the modern UI on top of it. Swagger still works if we add it back manually, but once we see Scalar in action, we'll probably wonder why we ever used Swagger at all.
This tutorial shows how to:
- Understand what changed in .NET 10 regarding Swagger and OpenAPI
- Install and configure Scalar in a .NET 10 Web API
- Understand the difference between OpenAPI generation and the UI layer
- Add XML documentation comments to enrich our Scalar docs
- Configure Scalar with auth support for JWT testing
- Set up multiple API versions with separate Scalar documents
- Migrate an existing project from Swagger to Scalar
What Actually Changed and Why
For years the default .NET Web API template included Swashbuckle.AspNetCore. This package handled two things — generating the OpenAPI JSON document and rendering the Swagger UI. Both in one package.
The problem was Swashbuckle is a community project. It fell behind on maintenance for a while. When Microsoft moved to OpenAPI 3.0 and then 3.1, Swashbuckle lagged. That dependency on a third-party project for something as critical as API documentation felt like a weak link.
Starting with .NET 9, Microsoft split these concerns cleanly :
- OpenAPI document generation →
Microsoft.AspNetCore.OpenApi— first-party, maintained by Microsoft, ships with .NET - API documentation UI → our choice. Scalar, Swagger UI, ReDoc — pick whatever we want.
In .NET 10, the native OpenAPI package now generates OpenAPI 3.1 documents by default. Full OpenAPI 3.1 means better nullable support, better JSON Schema, better tooling compatibility.
Scalar is the recommended UI for most new projects. Microsoft's own documentation shows Scalar examples. .NET 9 project templates actually shipped with Scalar by default for a period. It's the natural pairing.
Why Scalar Over Swagger UI
We've used Swagger UI for years. It works. But there are real differences worth knowing about.
Visual design — Swagger UI looks like something built in 2015. Functional but dated. Scalar looks like a modern API tool — clean dark and light modes, proper typography, well-organized sidebar. When we share our API docs with other teams or external developers, first impressions matter.
Request testing — Scalar's built-in request builder is significantly nicer than Swagger's "Try it out" feature. Better handling of auth headers, cleaner response display, easier parameter editing.
Auto-generated code samples — Scalar automatically generates code samples in multiple languages — Python, JavaScript, C#, Java, cURL, and more. Right there in the documentation. Swagger UI doesn't do this out of the box.
OpenAPI 3.1 support — Scalar supports OpenAPI 3.1 fully. Swagger UI's 3.1 support has been patchy depending on the version.
Performance — Scalar renders faster. On large APIs with hundreds of endpoints, Swagger UI can get sluggish. Scalar stays responsive.
Zero configuration — installing Scalar and adding two lines of code gives us a fully working UI. Swagger needed more ceremony.
Step 1 : Create a .NET 10 Web API Project
Create a new project :
dotnet new webapi -n MyApi --use-controllers
cd MyApi
Open Program.cs. We'll see the native OpenAPI setup is already there :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi(); // Native OpenAPI — already included
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // Exposes /openapi/v1.json
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
app.MapOpenApi() exposes the OpenAPI JSON at /openapi/v1.json. This is the document Scalar (or any UI) reads to build the documentation. No Swashbuckle needed — Microsoft generates this natively.
Step 2 : Install and Configure Scalar
Install the Scalar package :
dotnet add package Scalar.AspNetCore
Update Program.cs to add Scalar :
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // Generates /openapi/v1.json
app.MapScalarApiReference(); // Serves Scalar UI at /scalar/v1
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
That's the entire setup. Run the app and navigate to :
https://localhost:5001/scalar/v1
We'll see the Scalar UI with all our API endpoints listed, request testing built in, and code samples on the side. Two lines of code for a documentation experience that's better than Swagger after all its configuration.
Update launchSettings.json to open Scalar automatically when we start the project :
{
"profiles": {
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "scalar/v1",
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7001;http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Step 3 : Add XML Documentation to Enrich the Docs
Scalar reads XML documentation comments and shows them in the UI. This is how we add descriptions to endpoints, parameters, and responses without any attributes.
Enable XML documentation in our .csproj file :
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
</Project>
Tell the OpenAPI generator to include the XML file :
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info = new()
{
Title = "My API",
Version = "v1",
Description = "API for managing orders and customers",
Contact = new() { Name = "Dev Team", Email = "dev@ourcompany.com" }
};
return Task.CompletedTask;
});
});
Now add XML comments to our controllers :
/// <summary>
/// Manages customer orders
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
/// <summary>
/// Get all orders
/// </summary>
/// <remarks>Returns a list of all orders sorted by date descending.</remarks>
/// <returns>List of orders</returns>
/// <response code="200">Orders retrieved successfully</response>
/// <response code="401">Unauthorized — JWT token required</response>
[HttpGet]
[ProducesResponseType(typeof(List<OrderDto>), 200)]
[ProducesResponseType(401)]
public async Task<IActionResult> GetOrders()
{
// implementation
return Ok(new List<OrderDto>());
}
/// <summary>
/// Get order by ID
/// </summary>
/// <param name="id">The order ID</param>
/// <returns>Order details</returns>
/// <response code="200">Order found</response>
/// <response code="404">Order not found</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(OrderDto), 200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetOrder(int id)
{
// implementation
return Ok();
}
}
Scalar picks all of this up and displays it beautifully — endpoint descriptions, parameter descriptions, response codes with their meanings.
Step 4 : Configure JWT Auth in Scalar
If our API uses JWT authentication, we need Scalar to support sending the Bearer token in test requests. Configure it in Program.cs :
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
Create the transformer :
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
public class BearerSecuritySchemeTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken)
{
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>
{
["Bearer"] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "Enter your JWT token"
}
};
// Apply to all operations
foreach (var path in document.Paths.Values)
{
foreach (var operation in path.Operations.Values)
{
operation.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
}] = Array.Empty<string>()
});
}
}
return Task.CompletedTask;
}
}
Now Scalar shows an "Authenticate" button at the top. We paste our JWT token there and all test requests automatically include the Authorization: Bearer <token> header.
Step 5 : Multiple API Versions with Scalar
For APIs with v1 and v2, Scalar supports multiple documents. Here's a clean way to do this without API versioning middleware — just use [ApiExplorerSettings] :
// V1 Controller
[Route("api/v1/orders")]
[ApiExplorerSettings(GroupName = "v1")]
[ApiController]
public class OrdersV1Controller : ControllerBase
{
[HttpGet]
public IActionResult GetOrders() => Ok("V1 Orders");
}
// V2 Controller with extra features
[Route("api/v2/orders")]
[ApiExplorerSettings(GroupName = "v2")]
[ApiController]
public class OrdersV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetOrders(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20) => Ok("V2 Orders with pagination");
}
Register both OpenAPI documents :
builder.Services.AddOpenApi("v1");
builder.Services.AddOpenApi("v2");
// In app configuration:
app.MapOpenApi(); // /openapi/{documentName}.json
app.MapScalarApiReference("v1", options =>
{
options.WithTitle("My API v1")
.WithOpenApiRoutePattern("/openapi/v1.json");
});
app.MapScalarApiReference("v2", options =>
{
options.WithTitle("My API v2")
.WithOpenApiRoutePattern("/openapi/v2.json");
});
Now we have separate Scalar UIs at /scalar/v1 and /scalar/v2. Each shows only the endpoints for that version.
Step 6 : Migrate from Swagger to Scalar
If we have an existing project using Swashbuckle, migration is straightforward.
Remove Swashbuckle :
dotnet remove package Swashbuckle.AspNetCore
Add native OpenAPI and Scalar :
dotnet add package Scalar.AspNetCore
Replace in Program.cs :
// Remove these Swagger lines:
// builder.Services.AddSwaggerGen();
// app.UseSwagger();
// app.UseSwaggerUI();
// Add these instead:
builder.Services.AddOpenApi();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
Remove Swagger attributes we no longer need :
Most [SwaggerOperation], [SwaggerResponse] attributes can be replaced with standard XML comments and [ProducesResponseType] attributes. The native OpenAPI generator reads these directly.
Update launchSettings.json — change "launchUrl": "swagger" to "launchUrl": "scalar/v1".
That's the migration. For most projects it takes about 20 minutes.
Customizing the Scalar UI
Scalar has good customization options for themes and layout :
app.MapScalarApiReference(options =>
{
options
.WithTitle("Our API Documentation")
.WithTheme(ScalarTheme.DeepSpace) // Dark theme
.WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient)
.WithSidebar(true)
.WithSearchHotKey("k")
.HideClientButton(false);
});
Available themes include Default, Alternate, Moon, Purple, Solarized, BluePlanet, Saturn, Kepler, Mars, DeepSpace, and None. The dark themes especially look much better than anything Swagger UI offered.
Common Issues
/scalar/v1 gives 404
We called MapScalarApiReference() but not MapOpenApi(). Both are needed — MapOpenApi generates the JSON, MapScalarApiReference serves the UI that reads that JSON.
Scalar shows up in production
We have MapScalarApiReference() outside of the if (app.Environment.IsDevelopment()) block. Always wrap both MapOpenApi and MapScalarApiReference in that development check. API documentation should never be exposed in production.
XML comments not showing in Scalar
<GenerateDocumentationFile>true</GenerateDocumentationFile> isn't in the .csproj. Add it and rebuild.
Auth not working in Scalar test requests
We set up the security scheme in OpenAPI but forgot to click "Authenticate" in the Scalar UI and enter the token. Look for the lock icon or "Authenticate" button at the top of the Scalar UI.
Summary
We learned how to set up Scalar as our API documentation UI in .NET 10 and why it makes sense to switch from Swagger. We covered :
- What changed in .NET 10 — Microsoft.AspNetCore.OpenApi handles document generation natively, UI is our choice
- Why Scalar beats Swagger UI — better design, faster rendering, built-in code samples, full OpenAPI 3.1 support
- Installing Scalar with
dotnet add package Scalar.AspNetCore - Two-line setup with
app.MapOpenApi()andapp.MapScalarApiReference() - Adding XML documentation comments to enrich endpoint descriptions in Scalar
- Configuring JWT Bearer auth so we can test protected endpoints directly in Scalar
- Setting up multiple API versions with separate Scalar documents using
[ApiExplorerSettings] - Migrating an existing Swashbuckle project to native OpenAPI + Scalar in about 20 minutes
- Customizing Scalar with themes and display options
Swagger was the default — not necessarily the best. Now that Microsoft has replaced it with a first-party OpenAPI generator and Scalar is there as a polished UI layer, there's no real reason to reach for Swashbuckle in new projects. Give Scalar a try and we won't want to go back.
I hope you like this article...
Happy coding! 🚀
0 Comments