Skip to content

Update kobweb to v0.25.0 - #28

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/kobweb
Open

Update kobweb to v0.25.0#28
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/kobweb

Conversation

@renovate

@renovate renovate Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
com.varabyte.kobweb.application 0.23.30.25.0 age confidence
com.varabyte.kobwebx:silk-icons-mdi 0.23.30.25.0 age confidence
com.varabyte.kobwebx:silk-icons-fa 0.23.30.25.0 age confidence
com.varabyte.kobweb:silk-foundation 0.23.30.25.0 age confidence
com.varabyte.kobwebx:kobwebx-serialization-kotlinx 0.23.30.25.0 age confidence
com.varabyte.kobweb:kobweb-silk 0.23.30.25.0 age confidence
com.varabyte.kobweb:kobweb-core 0.23.30.25.0 age confidence

Release Notes

varabyte/kobweb (com.varabyte.kobwebx:silk-icons-mdi)

v0.25.0

This release is functionally identical to v0.24.1 but with all Kotlin / Compose dependencies migrated to latest.

[versions]
compose-html = "1.11.1"
compose-runtime = "1.11.2"
kobweb = "0.25.0"
kotlin = "2.4.0"

[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.


Full Changelog: varabyte/kobweb@v0.24.1...v0.25.0

v0.24.1

This is a jam packed release featuring long-planned tweaks to the network APIs as well as new lucide and material symbol icons!

In the previous release, we revisited the backend network APIs to support multipart requests, but due to backwards compatibility, we could not yet touch the frontend APIs, as the method names we wanted to use were already taken. Instead, we deprecated them.

In this release, those frontend changes are now done! Potentially, there may be some codebases out there which might break with this change, but they would have been ignoring scary deprecation warnings for a while now. As these methods are not used by most Kobweb users, we decided to incrementally update the release version instead of put out a new major version.

If you are a developer with a fullstack website and you get warnings after updating, please review the Notes section below about migrating. We did our best to tell the IDE how to automatically update your deprecated code to the latest versions of the API, but it doesn't always do the best job.

[!IMPORTANT]
Planning to upgrade? Review instructions in the README.

Changes
Frontend
  • Icons!

    • Added support for Google Material Symbols
    • Added support for Lucide
    • See the Notes section below for more details.
  • Introduced a slew of Kotlin-idiomatic network request APIs.

    • See the Notes section below for more details.
  • CSS Modifiers

    • Added support for setting various animation properties directly, e.g. Modifier.animation { delay(...) }
    • Added object-position property
    • Added text-wrap property
    • Added tab-size property
    • Added image-rendering property
    • Added image-orientation property
    • Added missing Transform.None value (and global keywords, e.g. Transform.Inherit)
  • Improved CSSPosition output, now producing simpler output

    • Before, CSSPosition values were expanded to their most specific 4-arg version, but now we shorten when possible.
    • e.g. CSSPosition(Edge.Left) is now just "left", and not "left 0% top 50%"
Worker
  • Made network APIs more flexible so they could be called from a worker script.

    • In a worker environment, accessing window throws a runtime exception. So now....
      • In a site: window.fetch(...)
      • In a worker: self.fetch(...)
  • Added support for launching coroutines inside a worker context

    • In a site: CoroutineContext(window.asCoroutineDispatcher()).launch { ... }
    • In a worker: CoroutineContext(self.asCoroutineDispatcher()).launch { ... }
Backend
  • You can now expose system properties from your build script to your backend server

    • This feature is kind of analogous to app globals for the frontend
    • See the Notes section below for more details.
  • Added support for multiple headers on the same request

    • Technically, requests can have a list of headers associated with a key, but we were collapsing them down to one.
  • When you run kobweb run --layout=static in dev mode, it will now return 404 on dynamic pages.

    • This should help avoid confusion that new users sometimes have where they have a dynamic page that would get correctly served for them locally during development time, only for their hosting service provider to return a 404 in prod.
Gradle
  • You can now specify an explicit path to a browser that gets used by the export step.
    • If set, will skip the normal downloading step that occurs on first export.
    • This can be useful in CIs (which create environments from scratch each time) or in any environment where you know that you already have a browser present that you can use.
    • You can set this value via environment variable: KOBWEB_EXPORT_BROWSER_PATH or directly in your site's build script via kobweb.app.export.browser.path.
Notes
Material Symbols

Kobweb has supported Material Design Icons for a long time, but Google deprecated those and replaced them with Material Symbols instead. If you are using MDI in your own project, you may want to consider migrating over if possible, as presumably MDI is mostly stale now and future work will go into MS.

# gradle/libs.versions.toml

[libraries]
silk-icons-ms = { module = "com.varabyte.kobwebx:silk-icons-ms", version.ref = "kobweb" }
// site/build.gradle.kts

kotlin {
    sourceSets {
        jsMain.dependencies {
            implementation(libs.silk.icons.ms)
        }
    }
}

At that point, you can reference them in your code! Material Symbol icon methods begin with the Ms prefix.

For example, if you wanted to use the mail icon, find the corresponding method with the same name:

MsMail()

You can also pass in a style, with one of three values (where outline is the default):

enum class MsIconStyle {
    OUTLINED,
    ROUNDED,
    SHARP;
}
Lucide Icons

Check out the official page to see their list of icons.

To use these in your project, simply add the following dependency to your build script:

# gradle/libs.versions.toml

[libraries]
silk-icons-lucide = { module = "com.varabyte.kobwebx:silk-icons-lucide", version.ref = "kobweb" }
// site/build.gradle.kts

kotlin {
    sourceSets {
        jsMain.dependencies {
            implementation(libs.silk.icons.lucide)
        }
    }
}

At that point, you can reference them in your code! Lucide icon methods begin with the Lucide prefix.

For example, if you wanted to use the mail icon, find the corresponding method with the same name:

LucideMail()

The full API for that method is:

@​Composable
fun LucideMail(
    modifier: Modifier = Modifier, size: CSSLengthValue = 1.em, strokeWidth: Number = 2, color: CSSColorValue? = null,
)

so feel free to play with those values in your own site.

Backend system properties

If you are running a kobweb server and want to expose some values to it from the build script, we made this easy:

kobweb {
   app {
      server {
         systemProperties.put("example.key", "example.value")
      }
   }
}

Then, anywhere inside your jvmMain server code:

val value = System.getProperty("example.key")!!
assertEquals("example.value", value)

We are using this new feature in the todo template example (e.g. kobweb create examples/todo). This allows us to make it easy for users to toggle the implementation of the backend datastore from in-memory to a MongoDB database. Feel free to check out the project if you are curious!

Revisiting Network APIs

The stdlib fetch API is very powerful but full of dynamic, type-unsafe code. Kobweb's network APIs are suspend friendly and type-safe.

However, in the earliest days of Kobweb development, I made an incorrect assumption and oversimplified what I would accept for a request's payload body. Specifically, I only accepted a byte array for it. However, there are several different data types that web request APIs traditionally accept, including multipart bodies, json, html text, and blobs.

Therefore, this version of Kobweb introduces a new RequestBody class which you construct with one of the various provided bodyOf factory methods. So where before you would pass raw bytes into one of the Kobweb network methods, now you pass in the output of a bodyOf call instead.

Of course there is a bodyOf for raw bytes. But we also support wrapping the other types as well (blobs, etc.)

And instead of returning raw bytes back, we now return a Response object (a standard class in web APIs). You can pull raw bytes out of its body payload by using our new bodyAsBytes extension method:

// ======== LEGACY ========
val bodyBytes: ByteArray = ...
val responseBytes = window.fetch(HttpMethod.POST, bodyBytes)

// ======== MODERN ========
val bodyBytes: ByteArray = ...
val body = bodyOf(bodyBytes)
val response = window.fetch(HttpMethod.POST, body)
val responseBytes = response.bodyAsBytes()

// Or, as a one-liner
val responseBytes = window.fetch(HttpMethod.POST, bodyOf(bodyBytes)).bodyAsBytes()

A little more verbose, admittedly, but now users can work with Response objects directly, which is a lot more powerful.

Real-world example: multipart request

To showcase a real example, the following code was introduced in the last release demonstrating how to send a multipart request:

// Using stdlib window fetch

val formData = FormData().apply {
   append("file", file, file.name)
   append("description", "Kobweb multipart test")
}
val requestInit = RequestInit(method = "POST", formData)
val response = window.fetch("/api/multipart", requestInit).await()

Now, I would write it as follows, ditching the RequestInit object and the need to await the response (since Kobweb's fetch method is suspend aware):

// Using Kobweb window.http

val formData = FormData().apply {
   append("file", file, file.name)
   append("description", "Kobweb multipart test")
}
val response = window.http.post("/api/multipart", bodyOf(formData))
Migrating deprecated network methods

We deprecated a lot of network methods in this release, since, as we discussed in the prior section, we settled on the approach we should have used in the beginning: take in a rich request body type and return a Response object.

If you are a user caught up in this, we apologize for the inconvenience.

In the previous release, we pushed people away from using, say, fetch, which originally returned raw bytes, and migrated them to use fetchBytes instead (same function, new name). Now, here we are abandoning raw bytes! And pushing users back to the original method, except now it takes in a rich body type and outputs a Response. Users are expected to call bodyAsBytes on top of the Response object to get those raw bytes out again.

For every method we deprecated, we included logic to help the IDE try to migrate the code for you. However, sometimes, it just really sucks at it, so manual migration / tweaking may be required.

In the following table, we'll use the post method to demonstrate before and after migrations, but keep in mind this same transition would apply for any of the HTTP verbs (get, put, delete, etc.).

Deprecated Modern
window.fetchBytes(HttpMethod.POST, url, bytes) window.fetch(HttpMethod.POST, bodyOf(bytes)).bodyAsBytes()
window.tryFetchBytes(HttpMethod.POST, url, bytes) window.tryFetch(HttpMethod.POST, bodyOf(bytes)) { bodyAsBytes() }
window.http.postBytes(url, bytes) window.http.post(bodyOf(bytes)).bodyAsBytes()
window.http.tryPostBytes(url, bytes) window.http.tryPost(bodyOf(bytes)) { bodyAsBytes() }
window.http.postBytes(url, bytes) window.http.post(bodyOf(bytes)).bodyAsBytes()
window.http.tryPostBytes(url, bytes) window.http.tryPost(bodyOf(bytes)) { bodyAsBytes() }
window.api.postBytes(url, bytes) window.api.post(bodyOf(bytes)).bodyAsBytes()
window.api.tryPostBytes(url, bytes) window.api.tryPost(bodyOf(bytes)) { bodyAsBytes() }
window.api.postBytes(url, bytes) window.api.post(bodyOf(bytes)).bodyAsBytes()
window.api.tryPostBytes(url, bytes) window.api.tryPost(bodyOf(bytes)) { bodyAsBytes() }

As you can see, the new versions are a bit more verbose, but by separating out the logic for making the request and reading the response, we get a lot more power with fewer methods.

Serialization extensions

Not many people know that Kobweb also provides an artifact (com.varabyte.kobwebx:kobwebx-serialization-kotlinx) that adds extensions for network requests that are Kotlinx serialization aware.

We also revisited those methods and simplified them as well.

For the following table, imagine we had the following serializable classes:

// Request goes from client -> server
@​Serializable
class Req {
   /* ... properties ... */
}

// Response goes from server -> client
@​Serializable
class Res {
   /* ... properties ... */
}

In the following table, note that a common change is moving the Res response class out from the initial request signature into a trailing bodyAs<T> call.

Deprecated Modern
window.http.postBytes<Req>(url, req) window.http.post<Req>(url, req).bodyAsBytes()
window.http.tryPostBytes<Req>(url, req) window.http.tryPost<Req>(url, req) { bodyAsBytes() }
window.http.post<Req, Res>(url, req) window.http.post<Req>(url, req).bodyAs<Res>()
window.http.post<Res>(url) window.http.post(url, body = null).bodyAs<Res>()
window.http.tryPost<Req, Res>(url, req) window.http.tryPost<Req>(url, req) { bodyAs<Res>() }
window.http.tryPost<Res>(url) window.http.tryPost(url, body = null) { bodyAs<Res>() }
window.api.postBytes<Req>(url, req) window.api.post<Req>(url, req).bodyAsBytes()
window.api.tryPostBytes<Req>(url, req) window.api.tryPost<Req>(url, req) { bodyAsBytes() }
window.api.post<Req, Res>(url, req) window.api.post<Req>(url, req).bodyAs<Res>()
window.api.post<Res>(url) window.api.post(url, body = null).bodyAs<Res>()
window.api.tryPost<Req, Res>(url, req) window.api.tryPost<Req>(url, req) { bodyAs<Res>() }
window.api.tryPost<Res>(url) window.api.tryPost(url, body = null) { bodyAs<Res>() }

The above tables certainly makes it look like this would be a very disruptive change indeed, but in practice, not too many people use fullstack Kobweb, and even for those that do, some of these methods are pretty niche, so we don't expect most users to even notice a single warning!

Thanks
  • @​Ayfri for their Lucide icons work
  • @​digitalby for Material Symbols
  • @​k4i6 for identifying the missing functionality of multiple header values and providing the feature.

The community really benefits from contributions like this. Thank you so much!


Full Changelog: varabyte/kobweb@v0.24.0...v0.24.1

v0.24.0

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).

[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.getSelection and document.getSelection
  • Added new SVG icons
  • Added holdsIn contracts to Modifier.thenIf/Unless and CssStyleVariant.thenIf/Unless
    • This allows the Kotlin compiler to smart-cast values within the then lambda block.
  • Fixed Modifier.displayBetween which 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 base block in their CssStyle block.
  • Added "...bytes" versions of frontend HTTP helper methods that return raw bytes (e.g. window.api.bytes(...) changed to window.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.Body and Response.Body APIs as a side effect
    • See Notes for more details.
  • Added a user data property on Request and Response classes

Markdown
  • Overhauled HTML handling in markdown to produce safer, more correct output
    • HTML attributes in markdown are now parsed and applied using attrs lambda 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 null error that could occur in the Gradle task listener
  • Internal build improvements (migrated away from kotlin-dsl plugin, 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!

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:

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:

@&#8203;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.

// 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.

// 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:

// Before
@&#8203;Api
fun endpoint(ctx: ApiContext) { ... }

// After
@&#8203;Api
suspend fun endpoint(ctx: ApiContext) { ... } 

Full Changelog: varabyte/kobweb@v0.23.3...v0.24.0


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about these updates again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot changed the title fix(deps): update kobweb to v0.24.0 Update kobweb to v0.24.0 Apr 8, 2026
@renovate
renovate Bot force-pushed the renovate/kobweb branch from e03bf54 to 24cc326 Compare July 2, 2026 23:33
@renovate renovate Bot changed the title Update kobweb to v0.24.0 Update kobweb to v0.24.1 Jul 2, 2026
@renovate
renovate Bot force-pushed the renovate/kobweb branch from 24cc326 to 9016d54 Compare July 5, 2026 22:10
@renovate renovate Bot changed the title Update kobweb to v0.24.1 Update kobweb to v0.25.0 Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants