This blog post is originally published on https://blog.elmah.io/swagger-vs-scalar-a-comprehensive-comparison-for-asp-net-core-developers/
Swagger UI used to come bundled with every new ASP.NET Core Web API project. Recent .NET versions replaced it with a bare OpenAPI document, leaving you to pick your own documentation UI, and Scalar has become one of the most popular choices. This post compares Swagger and Scalar, and walks through setting up each one in .NET 10.

You should test APIs before giving them to the frontend team. API testing and documenting them together is a tedious task, especially if the application is large enough. Many people prefer Postman documentation. However, there are graphical representations too that require a little less effort than documenting and testing your APIs. Swagger has been a long-time partner in interactive API documentation. The .NET 8 API template included Swashbuckle by default to provide both the OpenAPI JSON and the Swagger UI. However, later versions have brought novelty. The API template now has a default OpenAPI document, which developers typically pair with modern UI tools like Scalar.
Difference between Swagger and Swagger UI
There is a lot of confusion about the exact separation, even though I myself used to think they are the same. Swagger is an open-source ecosystem of tools developed by SmartBear. It provides tools for designing, building, and testing APIs using the OpenAPI specification.
- Swagger UI: A web-based interface consuming the OpenAPI document. It allows developers to visualize and test endpoints.
- Swagger Editor: A visual OpenAPI editor that allows writing and editing in YAML or JSON formats in a user-friendly manner.
- Swagger Codegen: An engine to generate server stubs and client SDKs in 40+ languages directly from OpenAPI definitions.
- SwaggerHub: A commercial platform for API design, collaboration, and governance of enterprise-grade API lifecycle management.
Why did Microsoft remove Swashbuckle from its default template?
Swashbuckle was a long-time companion of the ASP.NET Core standard and Microsoft's default for OpenAPI documentation. However, declining maintenance and departing contributors led to issues, and it had poor .NET 8 support. Microsoft removed it from default templates in .NET 9. In .NET 10, the third-party UI is no longer bundled by default in new Web API templates. Hence, .NET prioritizes developer experience and modernity by removing Swagger UI by default and allowing developers to opt for any other library that supports an OpenAPI document. Note that Scalar didn't replace OpenAPI; it provides a modern documentation UI for visualizing and interacting with your OpenAPI description.
Implementing Swagger UI and Scalar in ASP.NET Core API
Here's how each one is implemented in ASP.NET Core.
Step 1: Create project
dotnet new webapi SwaggerVsScalar
cd SwaggerVsScalarAs I am working on .NET 10, the default Program.cs will look.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}As you can see, OpenAPI support is built in by default.
Add Swagger UI
As we already have an endpoint, we can display it in Swagger.
Step 2: Add the package
dotnet add package Swashbuckle.AspNetCoreStep 3: Configure Program.cs
using Microsoft.OpenApi;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Swagger Demo API",
Version = "v1",
Description = "Demo API for comparing Swagger and Scalar"
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Swagger Demo API v1");
});
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}Swashbuckle.AspNetCore provides AddSwaggerGen() to register a service in the generation of the OpenAPI document, while SwaggerDoc specifies that an OpenAPI document called v1 should be generated using this metadata.
app.UseSwagger() makes the generated OpenAPI document available through HTTP and accessible at /swagger/v1/swagger.json.
The app.UseSwaggerUI is the actual Swagger UI and creates a web interface reading the document via the given path /swagger/v1/swagger.json. Finally, it displays it as an interactive API documentation page.
Step 4: Run the project
dotnet runThe Swagger UI is visible.

Add Scalar UI
Step 2: Add the package
dotnet add package Scalar.AspNetCoreStep 3: Configure program file
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}app.MapScalarApiReference() registers Scalar's API documentation UI as an endpoint in the ASP.NET Core application. Navigating to /scalar will open the Scalar interface by reading the document exposed by app.MapOpenApi().
Step 4: Run the project
dotnet runNow, the UI is accessible.

In the top-left corner is the menu that displays all endpoints using the OpenAPI document.

Scroll down to where the endpoints are displayed so we can test one.

Let's test by clicking the Test Request button.

Here's the response I got.


Attach OpenAPI metadata to the endpoint
You can add more details to the endpoint for the OpenAPI document.
app
.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithSummary("weather data for the state")
.WithDescription("This endpoint returns weather report for different counties.")
.WithTags("My place")
.Produces(200, typeof(object))
.Produces(400)
.ProducesProblem(500)
.RequireAuthorization();WithSummary provides a short summary for the endpoint, while WithDescription adds a longer description. WithTags categorizes endpoints, while the Produces fluent methods define successful and failure responses.

Import the Specification into Postman
The raw specification will be accessible at /openapi/v1.json, which is http://localhost:5282/openapi/v1.json in our case.

Postman can automatically build request collections from this native endpoint using the following steps:
- Open Postman and select the Import button in the top-left sidebar.
- Either import via Link: Choose the Link tab and paste the local URL.
- Or via File: Save the JSON from the browser, then drag the file into the Postman import window.

Our API document is imported.

Add a descriptive document with API controller
Now, we've seen how the minimal API looks with Scalar. Although for Swagger, you may already know what it looks like with controllers. I will show how Scalar works with the API.
Step 1: Create model
namespace SwaggerVsScalar.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}Step 2: Add controller
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SwaggerVsScalar.Models;
[ApiController]
[Route("api/[controller]")]
[Tags("Products")]
public class ProductsController : ControllerBase
{
private readonly List<Product> _products;
public ProductsController()
{
_products = new List<Product>
{
new Product { Id = 1, Name = "Matcha" },
new Product { Id = 2, Name = "Cappuccino" }
};
}
[HttpPost]
[EndpointName("CreateProduct")]
[EndpointSummary("Creates a new product")]
[EndpointDescription("This endpoint creates a new product and returns the created product.")]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult CreateProduct(Product input)
{
_products.Add(input);
return CreatedAtAction(
nameof(GetProduct),
new { id = input.Id },
input);
}
[HttpGet]
[EndpointName("GetProducts")]
[EndpointSummary("Gets all products")]
[EndpointDescription("This endpoint returns all available products.")]
[ProducesResponseType(typeof(IEnumerable<Product>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult GetProducts()
{
return Ok(_products);
}
[HttpGet("{id}")]
[EndpointName("GetProduct")]
[EndpointSummary("Gets a product by ID")]
[EndpointDescription("This endpoint returns a product matching the specified ID.")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetProduct(int id)
{
var result = _products.FirstOrDefault(x => x.Id == id);
if (result is null)
return NotFound();
return Ok(result);
}
}Here, we have a controller with some in-memory values. A noticeable addition is the description attributes [EndpointName], [EndpointSummary], [EndpointDescription], and [ProducesResponseType], which are alternatives to the fluent API methods we used with the minimal API.
Step 3: Configure Program.cs
There won't be much change from our previous setup. We just need to inject controllers so the API controllers get mapped. The final file looks like this:
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseHttpsRedirection();
app.Run();After running the project, we get the following result.

Each endpoint looks like this.

Get All.

GetById method.

Finally, we can see the model.

When to pick Swagger and when to pick Scalar?
We have to peel back the main discussion point: if Swagger is legendary and Scalar is modern, when to choose which? Scalar offers a modern developer experience with its search-oriented interface. Scalar specifically highlights responsive UI, dark mode, navigation, and improved handling of large APIs. Its built-in sidebar navigation for instant search is an advantage over the standard Swagger UI experience. Moreover, Scalar maintains request history, environment variables, code snippets and an advanced request configuration. You can customize themes with a wide range of options available by default. Such features make it a competitive choice for modern applications, a choice earlier competitors lacked.
Swagger is a mature and trusted option used by legacy systems. It has been powering APIs since 2011 and has earned developers' familiarity. It is simple and ideal for users who want to "Try it out". For a small and simple API project, Swagger suits better without adding a new tool to navigate and learn. Besides, an existing system that already uses Swagger shouldn't rush to switch to a new option, where the team may get tangled with learning another tool.
Conclusion
Scalar is a transformation in API documentation. For a long time, Swagger has been the default option for ASP.NET Core APIs that were developed using the template. However, in recent versions, Microsoft has removed the default Swagger and provided an OpenAPI document by default. The NuGet store offers several options for consuming the document, among which Scalar has emerged as a futuristic solution. Its features range from customized UI themes to search-friendly navigation. In this post, we discussed in depth the differences between Swagger and Scalar. We implemented each of them in .NET 10 to see the actual visual differences between them.
Code: https://github.com/elmahio-blog/SwaggerVsScalar








