| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| README.md | 2026-02-17 | 9.6 kB | |
| v0.24.0 source code.tar.gz | 2026-02-17 | 668.9 kB | |
| v0.24.0 source code.zip | 2026-02-17 | 1.1 MB | |
| Totals: 3 Items | 1.8 MB | 1 | |
In this update, we took some time to focus on the backend API, adding support for multipart requests. We also revisited the associated frontend APIs (and deprecated a bunch of them) paving the way for more improvements to follow.
This release also makes markdown handling more robust, and it raises the versions of kotlin to 2.3.10, compose (html) to 1.10.0, and compose (runtime) to 1.10.2 (now sourced from androidx).
:::toml
[versions]
compose-runtime = "1.10.2"
compose-html = "1.10.0"
kobweb = "0.24.0"
kotlin = "2.3.10"
[libraries]
compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "compose-html" }
compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose-runtime" }
[!IMPORTANT] Planning to upgrade? Review instructions in the README.
Changes
Frontend
- Added missing bindings to support
window.getSelectionanddocument.getSelection - Added new SVG icons
- Added
holdsIncontracts toModifier.thenIf/UnlessandCssStyleVariant.thenIf/Unless - This allows the Kotlin compiler to smart-cast values within the
thenlambda block. - Fixed
Modifier.displayBetweenwhich was broken since a prior rename - Improved validation for dynamic route segments
- Optional route segments (e.g.
{param?}) are now only allowed in the final position of a route - An error is shown if an optional segment is unnecessary (i.e. the non-optional route already exists)
- Conflicting dynamic route segments now produce an error at compile time
- Enabled the unused return value checker across the codebase
- Consider enabling the feature in your own project! For example, this can highlight a common mistake where people forget to put a
baseblock in theirCssStyleblock. - Added "...bytes" versions of frontend HTTP helper methods that return raw bytes (e.g.
window.api.bytes(...)changed towindow.api.getBytes(...)) - This more explicit naming convention will allow us to reclaim the existing methods in a future version but change them to return a more expressive return type.
Backend
- Added support for multipart request bodies.
- Updated
Request.BodyandResponse.BodyAPIs as a side effect -
See Notes for more details.
-
Added a user data property on
RequestandResponseclasses - This allows passing custom data around when using API interceptors
Markdown
- Overhauled HTML handling in markdown to produce safer, more correct output
- HTML attributes in markdown are now parsed and applied using
attrslambda blocks instead of string manipulation - Fixed multiple escaping issues (backslashes, dollar signs, newlines, html blocks, and inline html)
- Safely handle HTML attributes that use tick delimiters containing quotes
Gradle
- Fixed a
nullerror that could occur in the Gradle task listener - Internal build improvements (migrated away from
kotlin-dslplugin, added Gradle assignment plugin)
Misc
- Fixed crash when running using the Jetbrains Runtime JDK
- Migrated the Compose runtime dependency to androidx
- Updated many dependencies to their latest versions (Kotlin 2.3.10, KSP, ktor, Compose, and more)
- Thanks to fixes in these dependencies, you should now feel free to use the latest versions of Kotlin and Compose, without waiting for a new Kobweb release.
Thanks!
- @christomah0 contributed new SVG icons to the project.
Notes
Multipart requests
Requests using multipart forms is outside the scope of these release notes, but consider reading the official docs on the topic for more information.
Kobweb does not yet have an idiomatic way to send a multipart request (we will add this into a future release, but you can use the stdlib for this, leveraging FormData, RequestInit, and window.fetch APIs for now.)
The following FE code (imagine in a @Page somewhere) lets the user pick a file from their machine and send it to the server, using multipart forms to send both the file itself and an extra description as metadata:
:::kotlin
val scope = rememberCoroutineScope()
var filePath by remember { mutableStateOf("") }
Input(InputType.File, filePath, onValueChange = { filePath = it }, Modifier.id("file-input"))
Button(onClick = {
val fileInput = document.getElementById("file-input") as HTMLInputElement
val file = fileInput.files?.get(0)
if (file != null) {
scope.launch {
val formData = FormData().apply {
append("file", file, file.name)
append("description", "Kobweb multipart test")
}
val requestInit = RequestInit(method = "POST", body = formData)
val response = window.fetch("/api/multipart", requestInit).await()
if (response.ok) {
response.text().await().let { window.alert(it) }
} else {
window.alert("Upload failed: ${response.status}")
}
}
}
}
}
Then, to handle a multipart request on the backend, write an API endpoint that uses req.body.multipart() to query it. The following endpoint is just a demo and returns diagnostic information about what was sent back as body text:
:::kotlin
@Api
suspend fun multipart(ctx: ApiContext) {
val mp = ctx.req.body?.multipart() ?: return
ctx.res.body = Body.text(buildString {
appendLine("Received multipart request")
var i = 0
mp.forEachPart { part ->
appendLine("\nPart #${++i}")
appendLine("- Disposition: ${part.contentDisposition!!.disposition}")
appendLine("- Name: ${part.contentDisposition!!.name}")
(part.extras as? Multipart.Extras.File)?.let { fileExtras ->
appendLine("- Original file name: ${fileExtras.originalFileName}")
}
val bytes = part.bytes()
appendLine("- Size: ${bytes.size} bytes")
val maxSize = 100
if (bytes.size > maxSize) {
appendLine("- Content (first $maxSize bytes): ${bytes.take(maxSize).toByteArray().decodeToString().replace("\n", "\\n")}...")
} else {
appendLine("- Content: ${bytes.decodeToString().replace("\n", "\\n")}")
}
}
})
}
Our API is inspired by, but (we think) slightly more Kotlin idiomatic than, the ktor version.
Migrating frontend / backend APIs
Changes in this release caused us to reevaluate our APIs used for communicating between the frontend and backend, after adding multipart support challenged simple assumptions that I had mistakenly made a long time ago.
Fortunately, migration should be pretty easy, and it is hoped that most projects won't even notice the changes, or at least, will just need to accept IDE suggestions on deprecated lines of code.
HTTP responses expecting raw bytes
The APIs that send HTTP verb requests and expect raw bytes back have been renamed to be more explicit, e.g.
:::kotlin
// Before
window.api.get("get-endpoint").let { bytes -> ... }
window.api.post("post-endpoint").let { bytes -> ... }
// After
window.api.getBytes("get-endpoint").let { bytes -> ... }
window.api.postBytes("post-endpoint").let { bytes -> ... }
In a near future release of Kobweb, the old get, post, etc. methods will be repurposed to return a different, richer type.
Reading / writing body text
Before, we put read/write extension methods directly on the Request and Response objects, but body content can be more flexible than I originally realized, so I tweaked these APIs and moved them onto the exposed body objects instead.
:::kotlin
// Before
ctx.res.setBodyText(text)
val text = ctx.req.readBodyText()!!
// After
ctx.res.body = Body.text(text)
val text = ctx.req.body!!.text()
We also now support alternate ways of reading body content, i.e. body.stream() and body.bytes()
Querying body text is async now
Previously, Kobweb read the bytes out of the body itself on the backend and converted it into a string, but this was not compatible with some kind of response bodies. So now, we let the user query the body contents at their convenience.
If you run into an error that an API call you wrote cannot call a suspend method, this generally means you may have to update its signature to be explicitly suspend:
:::kotlin
// Before
@Api
fun endpoint(ctx: ApiContext) { ... }
// After
@Api
suspend fun endpoint(ctx: ApiContext) { ... }
Full Changelog: https://github.com/varabyte/kobweb/compare/v0.23.3...v0.24.0