
Transforms Ktor requests into cURL logs, enabling easy debugging and logging of HTTP requests. Offers customization for excluding or masking headers in generated logs. Inspired by Ok2Curl.
Transform Ktor HTTP requests into runnable curl commands for debugging and logging. Pure Kotlin
library supporting KMP (Android, iOS, JVM) and standard Android projects.
Inspired by Ok2Curl for OkHttp.
Add to your build.gradle.kts:
val commonMain by getting {
dependencies {
implementation("io.github.kabirnayeem99:ktor2curl:2.0.1")
}
}Supports all KMP targets: Android, iOS (arm64/x64/simulator), JVM, JS, WASM.
dependencies {
implementation("io.github.kabirnayeem99:ktor2curl:2.0.1")
}dependencies {
implementation 'io.github.kabirnayeem99:ktor2curl:2.0.1'
}After adding the dependency, run:
./gradlew buildIf you hit Kotlin version mismatch, ensure your project uses Kotlin 2.0+ ( see Troubleshooting).
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
println(curl) // or use Android Log.d(), your logging framework, etc.
}
}
}
}
// Every request via this client now logs a curl command
client.post("https://api.example.com/users") {
headers {
append(HttpHeaders.Authorization, "Bearer YOUR_TOKEN")
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
}
setBody("""{"name":"Alice"}""")
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
--data '{"name":"Alice"}'Hide API keys, tokens, and custom auth headers from logs:
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
Log.d("API", curl)
}
}
maskedHeaders = setOf(
HttpHeaders.Authorization,
"X-API-Key",
"X-Session-Token"
)
}
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/users \
-H "Authorization: [omitted]" \
-H "Content-Type: application/json" \
--data '{"name":"Alice"}'Remove User-Agent, Content-Length, or other headers from logs:
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
Log.d("API", curl)
}
}
excludedHeaders = setOf(
HttpHeaders.UserAgent,
HttpHeaders.ContentLength
)
maskedHeaders = setOf(HttpHeaders.Authorization)
}
}client.post("https://data.techforpalestine.org/api/v2/killed-in-gaza.min.json") {
headers {
append(HttpHeaders.Authorization, "Basic SXNyYWVsIGtpbGxzIGNoaWxkcmVuLg")
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
}
setBody("""{"en_name":"Mazen Ahmed Mohammed Al-Kahlout","name":"مازن أحمد محمد الكحلوت","age":52,"dob":"1972-02-05","sex":"m","id":"985194547","source":"u"}""")
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/killed-in-gaza.min.json \
-H "Authorization: [omitted]" \
-H "Content-Type: application/json" \
--data '{"en_name":"Mazen Ahmed Mohammed Al-Kahlout","name":"مازن أحمد محمد الكحلوت","age":52,"dob":"1972-02-05","sex":"m","id":"985194547","source":"u"}'| Property | Type | Default | Purpose |
|---|---|---|---|
converter |
CurlLogger |
Required | Implementation to handle curl output (log it, send it, store it) |
maskedHeaders |
Set<String> |
emptySet() |
Headers to replace with [omitted] (auth, tokens, keys) |
excludedHeaders |
Set<String> |
emptySet() |
Headers to skip entirely in output |
Send curl commands to a remote service, database, or aggregation tool:
class RemoteCurlLogger : CurlLogger {
override fun log(curl: String) {
// Send to your backend, analytics, or debug dashboard
analyticsService.trackRequest(curl)
}
}
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = RemoteCurlLogger()
}
}File uploads and form fields render correctly:
client.post("https://api.example.com/upload") {
setBody(
MultiPartFormDataContent(
formData {
append(
"file", InputProvider { "file content".byteInputStream() },
Headers.build { append(HttpHeaders.ContentDisposition, "filename=data.txt") })
append("field", "value")
}
))
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/upload \
-F "file=@data.txt" \
-F "field=value"Cause: Kotlin version mismatch or incorrect repository.
Fix:
kotlin = "2.3.0" or later in plugins blockrepositories {
mavenCentral()
google()
}Cause: Logger implementation not called.
Fix:
converter is set in plugin configval client = HttpClient {
install(KtorToCurl) { ... } // Must be before other requests
}Cause: Header not in maskedHeaders set.
Fix:
maskedHeaders = setOf(
"Authorization", // ✓ Correct
"X-Custom-Token",
HttpHeaders.Authorization // ✓ Also correct (from Ktor)
)Cause: Header name mismatch (case/spelling).
Fix:
HttpHeaders.HEADER_NAME constants from Ktor when possibleCause: Kotlin < 2.3.0 incompatible with Ktor 3.5 on Native.
Fix:
plugins {
kotlin("multiplatform") version "2.3.0"
}Contributions welcome. Before submitting:
git clone --recursive https://github.com/YOUR_USERNAME/ktor2curl.git)git checkout -b feature/add-cookie-maskingktor2curl/src/commonTest/ (see existing test structure)./gradlew :ktor2curl:jvmTest --console=plain./gradlew ktlintFormatgit clone https://github.com/YOUR_USERNAME/Ktor2Curl.git
cd Ktor2Curlgit remote add upstream https://github.com/kabirnayeem99/Ktor2Curl.git
git fetch upstreamgit checkout -b feature/your-ideaRun the full test suite to verify no regressions:
./gradlew :ktor2curl:jvmTest :ktor2curl:testDebugUnitTest :ktor2curl:iosSimulatorArm64TestOr just JVM for quick feedback:
./gradlew :ktor2curl:jvmTest --console=plainMIT - See LICENSE file for details.
Transform Ktor HTTP requests into runnable curl commands for debugging and logging. Pure Kotlin
library supporting KMP (Android, iOS, JVM) and standard Android projects.
Inspired by Ok2Curl for OkHttp.
Add to your build.gradle.kts:
val commonMain by getting {
dependencies {
implementation("io.github.kabirnayeem99:ktor2curl:2.0.1")
}
}Supports all KMP targets: Android, iOS (arm64/x64/simulator), JVM, JS, WASM.
dependencies {
implementation("io.github.kabirnayeem99:ktor2curl:2.0.1")
}dependencies {
implementation 'io.github.kabirnayeem99:ktor2curl:2.0.1'
}After adding the dependency, run:
./gradlew buildIf you hit Kotlin version mismatch, ensure your project uses Kotlin 2.0+ ( see Troubleshooting).
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
println(curl) // or use Android Log.d(), your logging framework, etc.
}
}
}
}
// Every request via this client now logs a curl command
client.post("https://api.example.com/users") {
headers {
append(HttpHeaders.Authorization, "Bearer YOUR_TOKEN")
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
}
setBody("""{"name":"Alice"}""")
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/users \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
--data '{"name":"Alice"}'Hide API keys, tokens, and custom auth headers from logs:
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
Log.d("API", curl)
}
}
maskedHeaders = setOf(
HttpHeaders.Authorization,
"X-API-Key",
"X-Session-Token"
)
}
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/users \
-H "Authorization: [omitted]" \
-H "Content-Type: application/json" \
--data '{"name":"Alice"}'Remove User-Agent, Content-Length, or other headers from logs:
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = object : CurlLogger {
override fun log(curl: String) {
Log.d("API", curl)
}
}
excludedHeaders = setOf(
HttpHeaders.UserAgent,
HttpHeaders.ContentLength
)
maskedHeaders = setOf(HttpHeaders.Authorization)
}
}client.post("https://data.techforpalestine.org/api/v2/killed-in-gaza.min.json") {
headers {
append(HttpHeaders.Authorization, "Basic SXNyYWVsIGtpbGxzIGNoaWxkcmVuLg")
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
}
setBody("""{"en_name":"Mazen Ahmed Mohammed Al-Kahlout","name":"مازن أحمد محمد الكحلوت","age":52,"dob":"1972-02-05","sex":"m","id":"985194547","source":"u"}""")
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/killed-in-gaza.min.json \
-H "Authorization: [omitted]" \
-H "Content-Type: application/json" \
--data '{"en_name":"Mazen Ahmed Mohammed Al-Kahlout","name":"مازن أحمد محمد الكحلوت","age":52,"dob":"1972-02-05","sex":"m","id":"985194547","source":"u"}'| Property | Type | Default | Purpose |
|---|---|---|---|
converter |
CurlLogger |
Required | Implementation to handle curl output (log it, send it, store it) |
maskedHeaders |
Set<String> |
emptySet() |
Headers to replace with [omitted] (auth, tokens, keys) |
excludedHeaders |
Set<String> |
emptySet() |
Headers to skip entirely in output |
Send curl commands to a remote service, database, or aggregation tool:
class RemoteCurlLogger : CurlLogger {
override fun log(curl: String) {
// Send to your backend, analytics, or debug dashboard
analyticsService.trackRequest(curl)
}
}
val client = HttpClient(CIO) {
install(KtorToCurl) {
converter = RemoteCurlLogger()
}
}File uploads and form fields render correctly:
client.post("https://api.example.com/upload") {
setBody(
MultiPartFormDataContent(
formData {
append(
"file", InputProvider { "file content".byteInputStream() },
Headers.build { append(HttpHeaders.ContentDisposition, "filename=data.txt") })
append("field", "value")
}
))
}Output:
curl -X POST \
https://data.techforpalestine.org/api/v2/upload \
-F "file=@data.txt" \
-F "field=value"Cause: Kotlin version mismatch or incorrect repository.
Fix:
kotlin = "2.3.0" or later in plugins blockrepositories {
mavenCentral()
google()
}Cause: Logger implementation not called.
Fix:
converter is set in plugin configval client = HttpClient {
install(KtorToCurl) { ... } // Must be before other requests
}Cause: Header not in maskedHeaders set.
Fix:
maskedHeaders = setOf(
"Authorization", // ✓ Correct
"X-Custom-Token",
HttpHeaders.Authorization // ✓ Also correct (from Ktor)
)Cause: Header name mismatch (case/spelling).
Fix:
HttpHeaders.HEADER_NAME constants from Ktor when possibleCause: Kotlin < 2.3.0 incompatible with Ktor 3.5 on Native.
Fix:
plugins {
kotlin("multiplatform") version "2.3.0"
}Contributions welcome. Before submitting:
git clone --recursive https://github.com/YOUR_USERNAME/ktor2curl.git)git checkout -b feature/add-cookie-maskingktor2curl/src/commonTest/ (see existing test structure)./gradlew :ktor2curl:jvmTest --console=plain./gradlew ktlintFormatgit clone https://github.com/YOUR_USERNAME/Ktor2Curl.git
cd Ktor2Curlgit remote add upstream https://github.com/kabirnayeem99/Ktor2Curl.git
git fetch upstreamgit checkout -b feature/your-ideaRun the full test suite to verify no regressions:
./gradlew :ktor2curl:jvmTest :ktor2curl:testDebugUnitTest :ktor2curl:iosSimulatorArm64TestOr just JVM for quick feedback:
./gradlew :ktor2curl:jvmTest --console=plainMIT - See LICENSE file for details.