Repository: JetBrains/kotlin-multiplatform-dev-docs
Pull request #337: feat: setting up deep links for Compose Multiplatform
Author: login: zamulla
Created: 2025-04-14T16:24:46Z | Merged: 2025-04-23T09:39:56Z



Diff:
diff --git a/mpd.tree b/mpd.tree
index d247ec79..32e9cd3c 100644
--- a/mpd.tree
+++ b/mpd.tree
@@ -54,6 +54,7 @@
 	    <toc-element toc-title="Navigation">
 			<toc-element topic="compose-navigation.md"/>
 			<toc-element topic="compose-navigation-routing.md"/>
+      <toc-element topic="compose-navigation-deep-links.md"/>
 		</toc-element>
 		<toc-element topic="compose-drag-drop.md"/>
 		<toc-element topic="compose-test.md"/>
diff --git a/topics/compose/compose-navigation-deep-links.md b/topics/compose/compose-navigation-deep-links.md
new file mode 100644
index 00000000..ef4caddb
--- /dev/null
+++ b/topics/compose/compose-navigation-deep-links.md
@@ -0,0 +1,391 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. [Register your deep link schema in the app configuration](#register-deep-links-schemas-in-the-operating-system)
+2. [Assign specific deep links to destinations in the navigation graph](#assign-deep-links-to-destinations)
+3. [Handle deep links received by the app](#handle-received-deep-links)
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}` matches `https://example.com/name=`
+  as well as any value of `name`).
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstBasePath = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstBasePath?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type
+            navDeepLink<Screen3>(basePath = "$firstBasePath/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+you need to put all parameters of the deep link URI pattern in a URL fragment (after the `#` character),
+and make sure that all parameters are URL-encoded.
+
+You can still use the `backStackEntry.toRoute()` method to parse the parameters if the URL fragment conforms
+to the URI pattern rules.
+For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser,
+see [](compose-navigation-routing.md#support-for-browser-navigation-in-web-apps).
+
+```kotlin
+composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // For the default Compose Multiplatform setup, localhost:8080
+            // is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
+            navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
+        )
+    ) { ... }
+```
+
+> Like with any other single-page web app, you can avoid using URL fragments on the web.
+> To do that, you have to configure your web server to redirect appropriate requests to the app
+> and rewrite the [default mapping of navigation route to the browser address](compose-navigation-routing.md#full-url-customization).
+>
+{style="tip"}
+
+## Handle received deep links
+
+On Android, the deep link URIs sent to the app are available as a part of the `Intent` that triggered the deep link.
+A cross-platform implementation needs a universal way to listen for deep links.
+
+Let's create a bare-bones implementation:
+
+1. Declare a singleton in common code for storing and caching the URIs with a listener for external URIs.
+2. Where necessary, implement platform-specific calls sending URIs received from the operating system.
+3. Set up the listener for new deep links in the main composable.
+
+### Declare a singleton with a URI listener
+
+In `commonMain`, declare the singleton object at the top level:
+
+```kotlin
+object ExternalUriHandler {
+    // Storage for when a URI arrives before the listener is set up
+    private var cached: String? = null
+    
+    var listener: ((uri: String) -> Unit)? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                // When a listener is set and `cached` is not empty,
+                // immediately invoke the listener with the cached URI
+                cached?.let { value.invoke(it) }
+                cached = null
+            }
+        }
+
+    // When a new URI arrives, cache it.
+    // If the listener is already set, invoke it and clear the cache immediately.
+    fun onNewUri(uri: String) {
+        cached = uri
+        listener?.let {
+            it.invoke(uri)
+            cached = null
+        }
+    }
+}
+```
+
+### Implement platform-specific calls to the singleton
+
+Both for desktop JVM and for iOS you need to explicitly pass the URI received from the system.
+
+In `jvmMain/.../main.kt`, parse the command-line arguments for every necessary operating system
+and pass the received URI on to the singleton:
+
+```kotlin
+// Import the singleton
+import org.company.app.ExternalUriHandler
+
+fun main() {
+    if(System.getProperty("os.name").indexOf("Mac") > -1) {
+        Desktop.getDesktop().setOpenURIHandler { uri ->
+            ExternalUriHandler.onNewUri(uri.uri.toString())
+        }
+    }
+    else {
+        ExternalUriHandler.onNewUri(args.getOrNull(0).toString())
+    }
+
+    application {
+         // ...
+    }
+}
+```
+
+For iOS, in Swift code add an `application()` variant that handles incoming URIs:
+
+```swift
+// Imports the KMP module to access the singleton
+import ComposeApp
+
+func application(
+    _ application: UIApplication,
+    open uri: URL,
+    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+) -> Bool {
+    // Sends the full URI on to the singleton
+    ExternalUriHandler.shared.onNewUri(uri: uri.absoluteString)    
+        return true
+    }
+```
+
+> For naming conventions for accessing singletons from Swift, see [Kotlin/Native documentation](https://kotlinlang.org/docs/native-objc-interop.html#kotlin-singletons).
+> 
+{style="tip"}
+
+### Set up the listener
+
+You can use a `DisposableEffect(Unit)` to set up the listener and clean it up after the composable is no longer active.
+For example:
+
+```kotlin
+internal fun App(navController: NavHostController = rememberNavController()) = AppTheme {
+
+    // The effect is produced only once, as `Unit` never changes
+    DisposableEffect(Unit) {
+        // Sets up the listener to call `NavController.navigate()`
+        // for the composable that has a matching `navDeepLink` listed
+        ExternalUriHandler.listener = { uri ->
+            navController.navigate(parseStringAsNavUri(uri))
+        }
+        // Removes the listener when the composable is no longer active
+        onDispose {
+            ExternalUriHandler.listener = null
+        }
+    }
+
+    // Reusing the example from earlier in this article
+    NavHost(
+        navController = navController,
+        startDestination = FirstScreen
+    ) {
+        // ...
+
+        composable<DeepLinkScreen>(
+            deepLinks = listOf(
+                navDeepLink { uriPattern = "$firstBasePath?name={name}" },
+                navDeepLink { uriPattern = "demo://example2.com/name={name}" },
+            )
+        ) {
+            // Composable content
+        }
+    }
+}
+```
+
+## Result
+
+Now you can see the full workflow:
+when the user opens a `demo://` URI, the operating system matches it with the registered scheme.
+Then:
+  * If the app handling the deep link is closed, the singleton receives the URI and caches it.
+    When the main composable function starts,
+    it calls the singleton and navigates to the deep link matching the cached URI.
+  * If the app handling the deep link is open, the listener is already set up, so when the singleton receives the URI
+    the app immediately navigates to it.
diff --git a/topics/whats-new/whats-new-compose-180.md b/topics/whats-new/whats-new-compose-180.md
index 44c4a76b..3a031d13 100644
--- a/topics/whats-new/whats-new-compose-180.md
+++ b/topics/whats-new/whats-new-compose-180.md
@@ -7,7 +7,7 @@ Here are the highlights for this EAP feature release:
 * [Preloading of resources for web targets](#preloading-of-resources)
 * [Integration with browser navigation controls](#browser-controls-supported-in-the-navigation-library)
 
-See the full list of changes for this release [on GitHub](https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.8.0-beta02).
+See the full list of changes for this release [on GitHub](https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.8.0-rc01).
 
 ## Breaking changes
 
@@ -20,16 +20,29 @@ only when using Kotlin 2.1.0 or newer.
 
 What this means for your projects, in addition to the underlying changes in the Compose compiler Gradle plugin:
 
+* For an app using libraries that depend on Compose Multiplatform:
+  it's recommended to update your project to Kotlin 2.1.20
+  and update the dependencies to versions compiled against Compose Multiplatform 1.8.0 and Kotlin 2.1.x.
 * For a library that depends on Compose Multiplatform:
-you need to update your project to Kotlin 2.1.x and Compose 1.8.0,
-then recompile the library and publish the new version.
-* For an app using such libraries:
-it's recommended to update your project to Kotlin 2.1.20
-and update the dependencies to versions compiled against Compose Multiplatform 1.8.0 and Kotlin 2.1.x.
+    you need to update your project to Kotlin 2.1.x and Compose 1.8.0,
+    then recompile the library and publish the new version.
 
 If you encounter any compatibility issues when upgrading to Compose Multiplatform 1.8.0,
 please let us know by filing an issue in [YouTrack](https://youtrack.jetbrains.com/newIssue?project=CMP).
 
+### Implicit dependency on `material-icons-core` removed
+
+Compose Multiplatform %composeEapVersion% incorporates a [change made in Material](https://android.googlesource.com/platform/frameworks/support/+/1d1abef790da93325a83fe19b50ccdec06be6956):
+there isn't an automatic dependency on `material-icons-core` anymore.
+This is in line with [moving away from dependencies built with K1](#full-migration-of-compose-multiplatform-to-the-k2-compiler).
+
+If you need to continue using the `material-icons-core` library in your project,
+add the dependency explicitly to your `build.gradle.kts`, for example:
+
+```kotlin
+implementation("org.jetbrains.compose.material:material-icons-core:1.6.11")
+```
+
 ### Migration from Bundle to SavedState in Navigation
 
 Navigation in Compose Multiplatform %composeEapVersion%,
@@ -173,6 +186,12 @@ The version of Skia used by Compose Multiplatform, via Skiko, has been updated t
 The previous version of Skia used was Milestone 126. You can see the changes
 made between these versions in the [release notes](https://skia.googlesource.com/skia/+/main/RELEASE_NOTES.md#milestone-132).
 
+### New Clipboard interface
+
+Compose Multiplatform has adopted Jetpack Compose’s new `Clipboard` interface.
+The previously used `ClipboardManager` interface, which was not accessible on web targets, has been deprecated in favor of
+`Clipboard`. The new interface supports `suspend` functions and is compatible with all targets, including web.
+
 ### Line-height alignment
 
 The common API for line-height alignment,
@@ -189,23 +208,6 @@ in Material3 components across all platforms unless otherwise specified.
 
 ## iOS
 
-### Drag-and-drop
-<secondary-label ref="Experimental"/>
-
-Compose Multiplatform for iOS introduces support for drag-and-drop functionality,
-allowing you to drag content into or out of a Compose application
-(see pull request [1690](https://github.com/JetBrains/compose-multiplatform-core/pull/1690) for a demo video).
-To define draggable content and drop targets, use the `dragAndDropSource` and `dragAndDropTarget` modifiers.
-
-On iOS, drag-and-drop session data is represented by [`UIDragItem`](https://developer.apple.com/documentation/uikit/uidragitem).
-This object contains information about cross-process data transfer and an optional local object for in-app usage.
-For example, you can use `DragAndDropTransferData(listOf(UIDragItem.fromString(text)))` to drag text,
-where `UIDragItem.fromString(text)` encodes the text into a format suitable for drag-and-drop operations.
-Currently, only the `String` and `NSObject` types are supported.
-
-For common use cases,
-see the [dedicated article](https://developer.android.com/develop/ui/compose/touch-input/user-interactions/drag-and-drop) in the Jetpack Compose documentation.
-
 ### Accessibility support improvements
 
 #### Support for right-to-left languages
@@ -324,6 +326,23 @@ The API that enables operating system logging on iOS is now stable. The `enableT
 experimental opt-in and now aligns with Android-style logging. This logging provides trace information that can be analyzed 
 using Xcode Instruments for debugging and performance analysis.
 
+### Drag-and-drop
+<secondary-label ref="Experimental"/>
+
+Compose Multiplatform for iOS introduces support for drag-and-drop functionality,
+allowing you to drag content into or out of a Compose application
+(see pull request [1690](https://github.com/JetBrains/compose-multiplatform-core/pull/1690) for a demo video).
+To define draggable content and drop targets, use the `dragAndDropSource` and `dragAndDropTarget` modifiers.
+
+On iOS, drag-and-drop session data is represented by [`UIDragItem`](https://developer.apple.com/documentation/uikit/uidragitem).
+This object contains information about cross-process data transfer and an optional local object for in-app usage.
+For example, you can use `DragAndDropTransferData(listOf(UIDragItem.fromString(text)))` to drag text,
+where `UIDragItem.fromString(text)` encodes the text into a format suitable for drag-and-drop operations.
+Currently, only the `String` and `NSObject` types are supported.
+
+For common use cases,
+see the [dedicated article](https://developer.android.com/develop/ui/compose/touch-input/user-interactions/drag-and-drop) in the Jetpack Compose documentation.
+
 ### Opt-in concurrent rendering
 <secondary-label ref="Experimental"/>
 
@@ -347,22 +366,6 @@ fun main(vararg args: String) {
 
 ## Web
 
-### Preloading of resources
-<secondary-label ref="Experimental"/>
-
-Compose Multiplatform %composeEapVersion% introduces a new experimental API
-for preloading fonts and images for web targets.
-Preloading helps
-prevent visual issues such as flashes of unstyled text (FOUT) or the flickering of images and icons.
-
-The following functions are now available for loading and caching resources:
-
-* `preloadFont()`, which preloads fonts.
-* `preloadImageBitmap()`, which preloads bitmap images.
-* `preloadImageVector()`, which preloads vector images.
-
-See the [documentation](compose-multiplatform-resources-usage.md#preload-resources-using-the-compose-multiplatform-preload-api) for details.
-
 ### Browser controls supported in the Navigation library
 
 In Kotlin/Wasm and Kotlin/JS applications built with Compose Multiplatform,
@@ -386,6 +389,22 @@ For example, you can assign different pointer icons to select text, open a conte
 
 Check out the full list of available [keywords](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor).
 
+### Preloading of resources
+<secondary-label ref="Experimental"/>
+
+Compose Multiplatform %composeEapVersion% introduces a new experimental API
+for preloading fonts and images for web targets.
+Preloading helps
+prevent visual issues such as flashes of unstyled text (FOUT) or the flickering of images and icons.
+
+The following functions are now available for loading and caching resources:
+
+* `preloadFont()`, which preloads fonts.
+* `preloadImageBitmap()`, which preloads bitmap images.
+* `preloadImageVector()`, which preloads vector images.
+
+See the [documentation](compose-multiplatform-resources-usage.md#preload-resources-using-the-compose-multiplatform-preload-api) for details.
+
 ## Desktop
 
 ### Support for Windows for ARM64
diff --git a/v.list b/v.list
index 14fdf503..706cea96 100644
--- a/v.list
+++ b/v.list
@@ -6,7 +6,7 @@
 
     <var name="composeVersion" value="1.7.3" type="string"/>
 
-    <var name="composeEapVersion" value="1.8.0-beta02" type="string"/>
+    <var name="composeEapVersion" value="1.8.0-rc01" type="string"/>
 
     <var name="kotlinVersion" value="2.0.0" type="string"/>
 


Reviews:
user: login: terrakok
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T12:48:04Z
user: login: zamulla
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T14:55:44Z
user: login: ElviraMustafina
body: LGTM 👍
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-23T04:07:31Z
user: login: zamulla
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T09:14:56Z
user: login: zamulla
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T09:16:53Z

Review comments:
diff_hunk: @@ -0,0 +1,379 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstUrl = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstUrl?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type.
+            navDeepLink<Screen3>(basePath = "$firstUrl/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+the deep link URI pattern needs to put all parameters in a URL fragment (after the `#` character),
+and the URL fragment needs to be URL-encoded.
path: topics/compose/compose-navigation-deep-links.md
user: login: terrakok
body: only screen parameters should be url-encoded
created_at: 2025-04-22T12:44:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,379 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstUrl = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstUrl?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type.
+            navDeepLink<Screen3>(basePath = "$firstUrl/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+the deep link URI pattern needs to put all parameters in a URL fragment (after the `#` character),
+and the URL fragment needs to be URL-encoded.
+
+You can still use the `backStackEntry.toRoute()` method to parse the parameters if the URL fragment conforms
+to the URI pattern rules.
+For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser,
+see [](compose-navigation-routing.md#support-for-browser-navigation-in-web-apps).
+
+```kotlin
+composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // For the default Compose Multiplatform setup, localhost:8080
+            // is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
+            navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
+        )
+    ) { ... }
+```
+
+## Handle received deep links
+
+On Android, the deep link URIs sent to the app are available as a part of the `Intent` that triggered the deep link.
+A cross-platform implementation needs a universal way to listen for deep links.
+
+Let's create a bare-bones implementation:
+
+1. Declare a singleton in common code for storing and caching the URIs with a listener for external URLs.
+2. Where necessary, implement platform-specific calls sending URIs received from the operating system.
+3. Set up the listener for new deep links in the main composable.
+
+### Declare a singleton with a URI listener
+
+In `commonMain`, declare the singleton object at the top level:
+
+```kotlin
+object ExternalUrlHandler {
+    // Storage for when a URI arrives before the listener is set up.
+    private var cached: String? = null
+    
+    var listener: ((url: String) -> Unit)? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                // When a listener is set and `cached` is not empty,
+                // immediately invoke the listener with the cached URI
+                cached?.let { value.invoke(it) }
+                cached = null
+            }
+        }
+
+    // When a new URI arrives, cache it.
+    // If the listener is already set, invoke it and clear the cache immediately.
+    fun onNewUrl(url: String) {
+        cached = url
+        listener?.let {
+            it.invoke(url)
+            cached = null
+        }
+    }
+}
+```
+
+### Implement platform-specific calls to the singleton
+
+Both for desktop JVM and for iOS you need to explicitly pass the URI received from the system.
+
+For desktop, in `jvmMain/.../main.kt`, add a `.setOpenURIHandler()` call to the `main()` function:
+
+```kotlin
+// Import the singleton
+import org.company.app.ExternalUrlHandler
+
+fun main() {
+    Desktop.getDesktop().setOpenURIHandler { url ->
path: topics/compose/compose-navigation-deep-links.md
user: login: terrakok
body: `setOpenURIHandler` I'm not sure it works in all linuxes. but I haven't experimented
created_at: 2025-04-22T12:47:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,379 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstUrl = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstUrl?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type.
+            navDeepLink<Screen3>(basePath = "$firstUrl/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+the deep link URI pattern needs to put all parameters in a URL fragment (after the `#` character),
+and the URL fragment needs to be URL-encoded.
+
+You can still use the `backStackEntry.toRoute()` method to parse the parameters if the URL fragment conforms
+to the URI pattern rules.
+For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser,
+see [](compose-navigation-routing.md#support-for-browser-navigation-in-web-apps).
+
+```kotlin
+composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // For the default Compose Multiplatform setup, localhost:8080
+            // is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
+            navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
+        )
+    ) { ... }
+```
+
+## Handle received deep links
+
+On Android, the deep link URIs sent to the app are available as a part of the `Intent` that triggered the deep link.
+A cross-platform implementation needs a universal way to listen for deep links.
+
+Let's create a bare-bones implementation:
+
+1. Declare a singleton in common code for storing and caching the URIs with a listener for external URLs.
+2. Where necessary, implement platform-specific calls sending URIs received from the operating system.
+3. Set up the listener for new deep links in the main composable.
+
+### Declare a singleton with a URI listener
+
+In `commonMain`, declare the singleton object at the top level:
+
+```kotlin
+object ExternalUrlHandler {
+    // Storage for when a URI arrives before the listener is set up.
+    private var cached: String? = null
+    
+    var listener: ((url: String) -> Unit)? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                // When a listener is set and `cached` is not empty,
+                // immediately invoke the listener with the cached URI
+                cached?.let { value.invoke(it) }
+                cached = null
+            }
+        }
+
+    // When a new URI arrives, cache it.
+    // If the listener is already set, invoke it and clear the cache immediately.
+    fun onNewUrl(url: String) {
+        cached = url
+        listener?.let {
+            it.invoke(url)
+            cached = null
+        }
+    }
+}
+```
+
+### Implement platform-specific calls to the singleton
+
+Both for desktop JVM and for iOS you need to explicitly pass the URI received from the system.
+
+For desktop, in `jvmMain/.../main.kt`, add a `.setOpenURIHandler()` call to the `main()` function:
+
+```kotlin
+// Import the singleton
+import org.company.app.ExternalUrlHandler
+
+fun main() {
+    Desktop.getDesktop().setOpenURIHandler { url ->
path: topics/compose/compose-navigation-deep-links.md
user: login: zamulla
body: For Windows and Linux I [changed it to simple args parsing](https://github.com/JetBrains/kotlin-multiplatform-dev-docs/pull/337/commits/f4df10a8f642e7bc08fb4695f38b1f8b0e3ddcc8#diff-844487cf640a25efb840dfbf0a201f4603d544b7ac542e4ae1a0997cb7bcc9b4R298).
created_at: 2025-04-22T14:55:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,390 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
path: topics/compose/compose-navigation-deep-links.md
user: login: ElviraMustafina
body: Please add links to the corresponding subsections
created_at: 2025-04-23T03:49:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,390 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstBasePath = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstBasePath?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type.
+            navDeepLink<Screen3>(basePath = "$firstBasePath/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+you need to put all parameters of the deep link URI pattern in a URL fragment (after the `#` character),
+and make sure that all parameters are URL-encoded.
+
+You can still use the `backStackEntry.toRoute()` method to parse the parameters if the URL fragment conforms
+to the URI pattern rules.
+For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser,
+see [](compose-navigation-routing.md#support-for-browser-navigation-in-web-apps).
+
+```kotlin
+composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // For the default Compose Multiplatform setup, localhost:8080
+            // is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
+            navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
+        )
+    ) { ... }
+```
+
+> Like with any other single-page web app, you can avoid using URL fragments on the web.
+> To do that, you have to configure your web server to redirect appropriate requests to the app
+> and rewrite the [default mapping of navigation route to the browser address](compose-navigation-routing.md#full-url-customization).
+>
+{style="tip"}
+
+## Handle received deep links
+
+On Android, the deep link URIs sent to the app are available as a part of the `Intent` that triggered the deep link.
+A cross-platform implementation needs a universal way to listen for deep links.
+
+Let's create a bare-bones implementation:
+
+1. Declare a singleton in common code for storing and caching the URIs with a listener for external URIs.
+2. Where necessary, implement platform-specific calls sending URIs received from the operating system.
+3. Set up the listener for new deep links in the main composable.
+
+### Declare a singleton with a URI listener
+
+In `commonMain`, declare the singleton object at the top level:
+
+```kotlin
+object ExternalUriHandler {
+    // Storage for when a URI arrives before the listener is set up
+    private var cached: String? = null
+    
+    var listener: ((uri: String) -> Unit)? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                // When a listener is set and `cached` is not empty,
+                // immediately invoke the listener with the cached URI
+                cached?.let { value.invoke(it) }
+                cached = null
+            }
+        }
+
+    // When a new URI arrives, cache it.
+    // If the listener is already set, invoke it and clear the cache immediately.
path: topics/compose/compose-navigation-deep-links.md
user: login: ElviraMustafina
body: Please remove dots (here and in ### Example of adding deep links to a destination)
created_at: 2025-04-23T03:54:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,390 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
path: topics/compose/compose-navigation-deep-links.md
user: login: ElviraMustafina
body: Let's lower the bar to something like:
Use the `.*` wildcard to match zero or more characters in a URI pattern. For example, `example.com/name={.*}` will match any character sequence after 'name='.

created_at: 2025-04-23T04:03:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,390 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
path: topics/compose/compose-navigation-deep-links.md
user: login: zamulla
body: I expanded the example a bit, but I think we're in danger of over-explaining here, examples are fairly straightforward and the URI pattern context clear.
created_at: 2025-04-23T09:14:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,390 @@
+[//]: # (title: Deep links)
+<primary-label ref="EAP"/>
+
+Deep linking is a navigation mechanism that allows the operating system to handle custom links
+by taking the user to a specific destination in the corresponding app.
+
+Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term):
+these are verified connections of the app with a specific web address.
+To learn about them specifically, see documentation on [Android App Links](https://developer.android.com/training/app-links)
+and [iOS universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).
+
+Deep links can also be useful for getting outside input into the app,
+for example, in the case of OAuth authorization:
+you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
+
+> Since outside input can be malicious, be sure to follow the [security guidelines](https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks)
+> to properly mitigate risks associated with processing raw deep link URIs.
+> 
+{style="warning"}
+
+To implement a deep link in Compose Multiplatform:
+
+1. Register your deep link schema in the app configuration.
+2. Assign specific deep links to destinations in the navigation graph.
+3. Handle deep links received by the app by converting them into navigation commands.
+
+## Setup
+
+To use deep links with Compose Multiplatform, set up the dependencies as follows.
+
+List these versions, libraries, and plugins in your Gradle catalog:
+
+```ini
+[versions]
+compose-multiplatform = "%composeEapVersion%"
+agp = "8.9.0"
+
+# The multiplatform Navigation library version with deep link support 
+androidx-navigation = "%composeNavigationEapVersion%"
+
+# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
+kotlin = "2.1.0"
+
+# Serialization library necessary to implement type-safe routes
+kotlinx-serialization = "1.7.3"
+
+[libraries]
+navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+[plugins]
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+android-application = { id = "com.android.application", version.ref = "agp" }
+```
+
+Add the additional dependencies to the shared module's `build.gradle.kts`:
+
+```kotlin
+plugins {
+    // ...
+    alias(libs.plugins.kotlinx.serialization)
+}
+
+// ...
+
+kotlin {
+    // ...
+    sourceSets {
+        commonMain.dependencies {
+            // ...
+            implementation(libs.androidx.navigation.compose)
+            implementation(libs.kotlinx.serialization.json)
+        }
+    }
+}
+```
+
+## Register deep links schemas in the operating system
+
+Each operating system has its own way of handling deep links.
+It's more reliable to refer to the documentation for your particular targets:
+
+* For Android apps, deep link schemes are declared as intent filters in the `AndroidManifest.xml` file.
+  See [Android documentation](https://developer.android.com/training/app-links/deep-linking?hl=en#adding-filters)
+  to learn how to properly set up intent filters.
+* For iOS and macOS apps, deep link schemes are declared in `Info.plist` files,
+    in the [CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleurltypes)
+    key.
+
+    > Compose Multiplatform [provides a Gradle DSL](compose-native-distribution.md#information-property-list-on-macos)
+    > to add values to a macOS app's `Info.plist`.
+    > For iOS, you can edit the file directly in your KMP project or [register schemes using the Xcode GUI](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app#Register-your-URL-scheme).
+    >
+    {style="note"}
+* For Windows apps, deep link schemes can be declared by adding [keys with necessary information to the Windows registry](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85))
+    (for Windows 8 and earlier)
+    or by specifying the [extension in the package manifest](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-uri-activation) (for Windows 10 and 11).
+    This can be done with an installation script or a third-party distribution package generator like [Hydraulic Conveyor](https://conveyor.hydraulic.dev/).
+    Compose Multiplatform does not support configuring this within the project itself.
+    
+    > Make sure you're not using one of the [schemes reserved by Windows](https://learn.microsoft.com/en-us/windows/apps/develop/launch/reserved-uri-scheme-names#reserved-uri-scheme-names).
+    >
+    {style="tip"}
+* For Linux, deep link schemes can be registered in a `.desktop` file included in the distribution.
+
+## Assign deep links to destinations
+
+A destination declared as a part of a navigation graph has an optional `deepLinks` parameter
+which can hold the list of corresponding `NavDeepLink` objects.
+Each `NavDeeplink` describes a URI pattern that should match a destination – you can define multiple URI patterns
+that should lead to the same screen.
+
+There is no limit to the number of deep links you can define for a route.
+
+### General URI patterns for deep links
+
+A general URI pattern should match the entire URI.
+You can use placeholders for parameters to extract them from the received URI within the destination.
+
+Rules for general URI patterns:
+
+* URIs without a scheme are assumed to start with `http://` or `https://`.
+  So `uriPattern = "example.com"` matches `http://example.com` and `https://example.com`.
+* `{placeholder}` matches one or more characters (`example.com/name={name}` matches `https://example.com/name=Bob`).
+  To match zero or more characters, use the `.*` wildcard (`example.com/name={.*}`)
+* Parameters for path placeholders are required while matching query placeholders is optional.
+  For example, the pattern `example.com/users/{id}?arg1={arg1}&arg2={arg2}`:
+    * Doesn't match `http://www.example.com/users?arg1=one&arg2=two` because the required part of the path (`id`) is missing.
+    * Matches both `http://www.example.com/users/4?arg2=two` and `http://www.example.com/users/4?arg1=one`.
+    * Also matches `http://www.example.com/users/4?other=random` because extraneous query parameters don't affect matching.
+* If several composables have a `navDeepLink` that matches the received URI, behavior is indeterminate.
+  Make sure that your deep link patterns don't intersect.
+  If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters,
+  or use an intermediate destination to route the user predictably.
+
+### Generated URI pattern for a route type
+
+You can avoid writing out the URI pattern fully:
+the Navigation library can automatically generate a URI pattern based on the parameters of a route.
+
+To use this approach, define a deep link like this:
+
+```kotlin
+composable<PlantDetail>(
+    deepLinks = listOf(
+        navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
+    )
+) { ... }
+```
+
+Here `PlantDetail` is the route type you're using for the destination, and the "plant" in `basePath`
+is the serial name of the `PlantDetail` data class.
+
+The rest of the URI pattern will be generated as follows:
+
+* Required parameters are appended as path parameters (example: `/{id}`)
+* Parameters with a default value (optional parameters) are appended as query parameters (example: `?name={name}`)
+* Collections are appended as query parameters (example: `?items={value1}&items={value2}`)
+* The order of parameters matches the order of the fields in the route definition.
+
+So, for example, this route type:
+
+```kotlin
+@Serializable data class PlantDetail(
+  val id: String,
+  val name: String,
+  val colors: List<String>,
+  val latinName: String? = null,
+)
+```
+
+has the following generated URI pattern generated by the library:
+
+```none
+<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
+```
+
+### Example of adding deep links to a destination
+
+In this example we assign several deep links to a destination and then
+extract parameter values from the received URIs:
+
+```kotlin
+@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
+
+// ...
+
+val firstBasePath = "demo://example1.org"
+
+NavHost(
+    navController = navController,
+    startDestination = FirstScreen
+) {
+    // ...
+    
+    composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // This composable should handle links both for demo://example1.org and demo://example2.org
+            navDeepLink { uriPattern = "$firstBasePath?name={name}" },
+            navDeepLink { uriPattern = "demo://example2.org/name={name}" },
+            // The generated pattern only handles the parameters,
+            // so we add the serial name for the route type.
+            navDeepLink<Screen3>(basePath = "$firstBasePath/dlscreen"),
+        )
+    ) {
+        // If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
+        // it matches the generated URI pattern (name is a required parameter and is given in the path),
+        // and you can map it to the route type automatically
+        val deeplink: DeepLinkScreen = backStackEntry.toRoute()
+        val nameGenerated = deeplink.name
+        
+        // If the app receives a URI matching only a general pattern,
+        // like `demo://example1.com/?name=Jane`
+        // you need to parse the URI directly
+        val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
+        
+        // Composable content
+    }
+}
+```
+
+For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps,
+you need to put all parameters of the deep link URI pattern in a URL fragment (after the `#` character),
+and make sure that all parameters are URL-encoded.
+
+You can still use the `backStackEntry.toRoute()` method to parse the parameters if the URL fragment conforms
+to the URI pattern rules.
+For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser,
+see [](compose-navigation-routing.md#support-for-browser-navigation-in-web-apps).
+
+```kotlin
+composable<DeepLinkScreen>(
+        deepLinks = listOf(
+            // For the default Compose Multiplatform setup, localhost:8080
+            // is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
+            navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
+        )
+    ) { ... }
+```
+
+> Like with any other single-page web app, you can avoid using URL fragments on the web.
+> To do that, you have to configure your web server to redirect appropriate requests to the app
+> and rewrite the [default mapping of navigation route to the browser address](compose-navigation-routing.md#full-url-customization).
+>
+{style="tip"}
+
+## Handle received deep links
+
+On Android, the deep link URIs sent to the app are available as a part of the `Intent` that triggered the deep link.
+A cross-platform implementation needs a universal way to listen for deep links.
+
+Let's create a bare-bones implementation:
+
+1. Declare a singleton in common code for storing and caching the URIs with a listener for external URIs.
+2. Where necessary, implement platform-specific calls sending URIs received from the operating system.
+3. Set up the listener for new deep links in the main composable.
+
+### Declare a singleton with a URI listener
+
+In `commonMain`, declare the singleton object at the top level:
+
+```kotlin
+object ExternalUriHandler {
+    // Storage for when a URI arrives before the listener is set up
+    private var cached: String? = null
+    
+    var listener: ((uri: String) -> Unit)? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                // When a listener is set and `cached` is not empty,
+                // immediately invoke the listener with the cached URI
+                cached?.let { value.invoke(it) }
+                cached = null
+            }
+        }
+
+    // When a new URI arrives, cache it.
+    // If the listener is already set, invoke it and clear the cache immediately.
path: topics/compose/compose-navigation-deep-links.md
user: login: zamulla
body: These are two different sentences, I refuse 😁 a single sentence comment is one thing, but removing a period between two sentences is going directly against grammar.
created_at: 2025-04-23T09:16:53Z
author_association: COLLABORATOR

PR comments:
user: login: ApoloApps
created_at: 2025-04-16T10:53:07Z
body: coul you add example for web wasm? btw,  
```
Desktop.getDesktop().setOpenURIHandler { url ->
        // Call the `onNewUrl` function with the URI from the `OpenURIEvent` object.
        ExternalUrlHandler.onNewUrl(url.uri.toString())
    } 
```
does not work on jvm windows because setOpenURIHandler is not supported and will crash. thanks!
author_association: NONE
user: login: zamulla
created_at: 2025-04-17T12:57:53Z
body: @ApoloApps

> could you add example for web wasm? 

Can you describe a use case for this? As far as I understand, on the web we generally don't need deep links because we have, well, links.
 
> does not work on jvm windows because setOpenURIHandler is not supported and will crash. thanks!

That's a very good point, thanks! We'll adjust the example to be conditional here.

author_association: COLLABORATOR
user: login: ApoloApps
created_at: 2025-04-17T13:55:58Z
body: > Can you describe a use case for this? As far as I understand, on the web we generally don't need deep links because we have, well, links.

Links should represent compose pages (in single project artifact, flutter lets you do it https://app.flutterflow.io/project/test-1weenr, and it opens the corresponding project page with id test-1weenr). Eg: https://composevisual.com/project/ABCDEFG123/edit-theme should point to page edit theme in project ABCDEFG123. Maybe this is not the place to ask but it seems rational to me to include this usecase.

EDIT: and without the prepending "#"
author_association: NONE
user: login: zamulla
created_at: 2025-04-22T12:45:50Z
body: @ApoloApps yep, this one I get.

We have a [separate section on browser navigation](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-navigation-routing.html#support-for-browser-navigation-in-web-apps) but I understand the deep link connection now, thanks, will add a bit about that.

As for using `#`, for SPA you can't really avoid it without server configuration, but we'll probably mention that at least.
author_association: COLLABORATOR