| name | openapi-scalar |
| description | Use when setting up OpenAPI spec generation or Scalar API documentation UI. |
| metadata | {"category":"api","agent":"api-designer"} |
| when_to_use | When configuring OpenAPI spec generation or Scalar API documentation UI |
OpenAPI & Scalar API Documentation
Core Principles
- Use native
Microsoft.AspNetCore.OpenApi (.NET 9+) instead of Swashbuckle
- Configure document transformers for metadata, security schemes, and customization
- Use Scalar as the modern API documentation UI replacement for Swagger UI
- Add OpenAPI metadata to every endpoint for accurate documentation
- Protect documentation endpoints in production
Patterns
Native OpenAPI Setup (.NET 9+)
builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info = new OpenApiInfo
{
Title = "{Domain} API",
Version = "v1",
Description = "API for {Company} {Domain} management"
};
return Task.CompletedTask;
});
options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
Bearer Security Scheme Transformer
internal sealed class BearerSecuritySchemeTransformer(
IAuthenticationSchemeProvider schemeProvider)
: IOpenApiDocumentTransformer
{
public async Task TransformAsync(
OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken ct)
{
var schemes = await schemeProvider.GetAllSchemesAsync();
if (schemes.Any(s =>
s.Name == JwtBearerDefaults.AuthenticationScheme))
{
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes["Bearer"] =
new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "Enter JWT token"
};
}
}
}
Scalar UI Configuration
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options
.WithTitle("{Domain} API")
.WithTheme(ScalarTheme.BluePlanet)
.WithDefaultHttpClient(
ScalarTarget.CSharp, ScalarClient.HttpClient)
.WithPreferredScheme("Bearer")
.WithHttpBearerAuthentication(bearer =>
{
bearer.Token = "your-dev-token-here";
});
});
}
if (!app.Environment.IsDevelopment())
{
app.MapOpenApi()
.RequireAuthorization("ApiDocAccess");
app.MapScalarApiReference()
.RequireAuthorization("ApiDocAccess");
}
Versioned API Documents
builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((doc, _, _) =>
{
doc.Info.Title = "{Domain} API v1";
doc.Info.Version = "1.0";
return Task.CompletedTask;
});
});
builder.Services.AddOpenApi("v2", options =>
{
options.AddDocumentTransformer((doc, _, _) =>
{
doc.Info.Title = "{Domain} API v2";
doc.Info.Version = "2.0";
return Task.CompletedTask;
});
});
app.MapOpenApi();
Endpoint Metadata
app.MapGet("/orders/{id}", GetOrder)
.WithSummary("Get order by ID")
.WithDescription("Returns full order details including line items")
.Produces<OrderResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.WithTags("Orders");
[HttpGet("{id:guid}")]
[EndpointSummary("Get order by ID")]
[EndpointDescription("Returns full order details")]
[ProducesResponseType(typeof(OrderResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<OrderResponse>> GetOrder(Guid id) { }
Build-Time Document Generation
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" />
dotnet build
Anti-Patterns
- Using Swashbuckle with .NET 9+ (use native OpenAPI instead)
- Missing security scheme documentation
- No endpoint summaries or descriptions
- Exposing API docs without auth in production
- Hardcoding server URLs in OpenAPI document
Detect Existing Patterns
- Search for
AddOpenApi in Program.cs (native .NET 9+)
- Search for
AddSwaggerGen (Swashbuckle — legacy, migration candidate)
- Check for
Scalar.AspNetCore package in .csproj
- Look for
MapScalarApiReference or MapSwagger calls
- Check for
Microsoft.AspNetCore.OpenApi package reference
Adding to Existing Project
- Replace Swashbuckle with
Microsoft.AspNetCore.OpenApi (if on .NET 9+)
- Add Scalar —
dotnet add package Scalar.AspNetCore
- Configure OpenAPI with document transformers for metadata
- Add security scheme transformer for JWT/API key
- Add metadata to all endpoints (
WithSummary, WithTags)
- Protect docs in production with authorization
Migration from Swashbuckle
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API" });
});
app.UseSwagger();
app.UseSwaggerUI();
builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((doc, _, _) =>
{
doc.Info.Title = "My API";
return Task.CompletedTask;
});
});
app.MapOpenApi();
app.MapScalarApiReference();
References