OpenAPI (Swagger) specification generator for Ktor
📖 Full Documentation • Quick Start • Features • Configuration • Usage Guide
InspeKtor is a Kotlin compiler plugin that automatically generates OpenAPI (Swagger) specifications from your Ktor server code at build time. No runtime overhead, no special DSL wrappers, no code modifications required.
Simply annotate your route definitions with @GenerateOpenApi and get a complete openapi.yaml or openapi.json
specification.
| Traditional Approach | InspeKtor | |
|---|---|---|
| Setup | Manual spec writing or complex DSL wrappers | Single annotation |
| Maintenance | Spec drifts from code over time | Always in sync - generated from source |
| Performance | Runtime overhead for spec generation | Zero runtime cost - build time only |
| Learning Curve | Learn new DSL or spec format | Use your existing Ktor code |
// build.gradle.kts
plugins {
id("io.github.tabilzad.inspektor") version "0.10.0-alpha"
}@GenerateOpenApi
fun Application.module() {
routing {
get("/hello") {
call.respondText("Hello, World!")
}
post("/users") {
val user = call.receive<CreateUserRequest>()
call.respond(HttpStatusCode.Created, user)
}
}
}
data class CreateUserRequest(val name: String, val email: String)./gradlew buildYour OpenAPI specification is generated at build/resources/main/openapi/openapi.yaml
Want a complete example? Check out the Sample Project
| Feature | Type | Description |
|---|---|---|
| Path & Endpoint Detection | Automatic | Extracts all routes from annotated functions |
| Ktor Resources Support | Automatic | Full support for type-safe routing |
| Request Body Schemas | Automatic | Generates schemas from call.receive<T>() |
| Response Schemas | Explicit | Define with responds<T>() or @KtorResponds |
| Descriptions | Explicit | Add summaries via @KtorDescription or KDocs |
| Tags | Explicit | Organize endpoints with @Tag |
| Security Schemes | Configuration | JWT, API Key, OAuth2, etc. |
| Sealed Classes | Automatic | oneOf with discriminators |
| Value Classes | Automatic | Unwrapped to underlying type |
| Generic Types | Automatic | Full support for parameterized types |
swagger {
documentation {
info {
title = "My API"
description = "API documentation for my Ktor server"
version = "1.0.0"
contact {
name = "API Support"
url = "https://example.com/support"
}
}
servers = listOf("https://api.example.com", "http://localhost:8080")
}
pluginOptions {
format = "yaml" // or "json"
}
}| Option | Default | Description |
|---|---|---|
info.title |
"Open API Specification" |
API specification title |
info.description |
"Generated using Ktor Docs Plugin" |
API description |
info.version |
"1.0.0" |
API version |
generateRequestSchemas |
true |
Auto-resolve request body schemas |
hideTransientFields |
true |
Omit @Transient fields from schemas |
hidePrivateAndInternalFields |
true |
Omit private/internal fields |
deriveFieldRequirementFromTypeNullability |
true |
Nullable = optional, non-null = required |
useKDocsForDescriptions |
true |
Extract descriptions from KDoc comments |
servers |
[] |
List of server URLs |
| Option | Default | Description |
|---|---|---|
enabled |
true |
Enable/disable the plugin |
saveInBuild |
true |
Save spec in build/ directory |
format |
"yaml" |
Output format: yaml or json |
filePath |
build/resources/main/openapi/ |
Custom output path |
regenerationMode |
"strict" |
Incremental build behavior (details) |
Annotate route functions to generate their specifications:
@GenerateOpenApi
fun Route.usersApi() {
route("/api/v1/users") {
get { /* List users */ }
post { /* Create user */ }
route("/{id}") {
get { /* Get user by ID */ }
put { /* Update user */ }
delete { /* Delete user */ }
}
}
}You can also annotate entire application modules:
@GenerateOpenApi
fun Application.apiModule() {
routing {
usersApi() // All routes are included
productsApi() // Even nested route functions
ordersApi()
}
}Use @KtorDescription for endpoint documentation:
@GenerateOpenApi
fun Route.ordersApi() {
route("/orders") {
@KtorDescription(
summary = "Create Order",
description = "Creates a new order with the provided items"
)
post {
val order = call.receive<CreateOrderRequest>()
// ...
}
}
}Use @KtorSchema and @KtorField for schema documentation:
@KtorSchema("Request payload for creating a new order")
data class CreateOrderRequest(
@KtorField("List of items to include in the order")
val items: List<OrderItem>,
@KtorField("Optional discount code")
val discountCode: String? = null
)Or simply use KDoc comments:
/**
* Request payload for creating a new order
* @property items List of items to include in the order
* @property discountCode Optional discount code
*/
data class CreateOrderRequest(
val items: List<OrderItem>,
val discountCode: String? = null
)Option 1: Inline DSL (Recommended)
post("/orders") {
responds<Order>(HttpStatusCode.Created, description = "Order created successfully")
responds<ErrorResponse>(HttpStatusCode.BadRequest, description = "Invalid request")
respondsNothing(HttpStatusCode.Unauthorized)
// Your handler code...
}Option 2: Annotation
@KtorResponds(
[
ResponseEntry("201", Order::class, description = "Order created"),
ResponseEntry("400", ErrorResponse::class, description = "Invalid request"),
ResponseEntry("401", Nothing::class)
]
)
post("/orders") { /* ... */ }Group endpoints by applying tags:
@Tag(["Users"])
fun Route.usersApi() {
get("/users") { /* tagged as "Users" */ }
post("/users") { /* tagged as "Users" */ }
}
// Or on individual endpoints:
@GenerateOpenApi
fun Route.api() {
@Tag(["Users"])
get("/users") { /* ... */ }
@Tag(["Products"])
get("/products") { /* ... */ }
}Define security schemes in your Gradle configuration:
swagger {
documentation {
security {
schemes {
"bearerAuth" to SecurityScheme(
type = "http",
scheme = "bearer",
bearerFormat = "JWT"
)
"apiKey" to SecurityScheme(
type = "apiKey",
`in` = "header",
name = "X-API-Key"
)
}
scopes {
or { +"bearerAuth" }
or { +"apiKey" }
}
}
}
}Customize how specific types appear in the schema:
swagger {
documentation {
serialOverrides {
typeOverride("java.time.Instant") {
serializedAs = "string"
format = "date-time"
description = "ISO 8601 timestamp"
}
typeOverride("java.util.UUID") {
serializedAs = "string"
format = "uuid"
}
}
}
}Sealed classes are automatically handled with oneOf:
@JsonClassDiscriminator("type")
sealed class PaymentMethod {
data class CreditCard(val cardNumber: String, val cvv: String) : PaymentMethod()
data class BankTransfer(val iban: String) : PaymentMethod()
}Configure the default discriminator property:
swagger {
documentation {
polymorphicDiscriminator = "type" // default
}
}The plugin supports three regeneration modes to balance build speed and specification completeness:
| Mode | Build Speed | Completeness | Recommended For |
|---|---|---|---|
strict |
Slower | Always complete | CI/CD, releases |
safe |
Balanced | Usually complete | Local development |
fast |
Fastest | May be partial | Rapid prototyping |
swagger {
pluginOptions {
regenerationMode = "strict" // default
}
}strict (Default): Regenerates the full spec on every build. Best for CI/CD pipelines.
safe: Regenerates when files containing @GenerateOpenApi change. Good balance for development.
fast: Trusts Kotlin's incremental compilation. Fastest, but may produce incomplete specs.
| Plugin Version | Kotlin Version |
|---|---|
| 0.10.0-alpha | 2.3.0 |
| 0.8.8-alpha | 2.2.20, 2.2.21 |
| 0.8.7-alpha | 2.2.20 |
| 0.8.4-alpha | 2.2.0 |
| 0.8.0-alpha | 2.1.20 |
| 0.7.0-alpha | 2.1.0 |
| 0.6.4-alpha | 2.0.20 |
| 0.6.0-alpha | 2.0 |
- Automatic response type inference from handler code
- Auto-tagging based on module/route function names
- Tag descriptions
- OpenAPI 3.1 full support
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Full Documentation - Comprehensive guides, API reference, and examples
- GitHub Issues - Bug reports and feature requests
- Sample Project - Complete working example
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
