# How to Implement Deep Linking with StackNavigation in Decompose

> Learn to implement deep linking in Decompose using StackNavigation. Parse URLs into components and build your navigation stack efficiently.

- Repository: [Arkadii Ivanov/decompose](https://github.com/arkivanov/decompose)
- Tags: how-to-guide
- Published: 2026-02-25

---

**Decompose supports deep linking by parsing a URL into path segments and query parameters, then passing the resulting `Url` object through the component hierarchy to build the initial navigation stack.**

Decompose is a Kotlin Multiplatform library for building component-based architectures with navigation. Implementing **deep linking with StackNavigation in Decompose** requires parsing incoming URLs and mapping them to your navigation configuration tree. The library provides `childStackWebNavigation` and URL parsing utilities to bridge browser history with your component stack.

## Parse Incoming URLs into Path Segments

Before components can handle deep links, you must parse the raw URL into a structured format. In [`src/commonMain/kotlin/com/arkivanov/sample/shared/Url.kt`](https://github.com/arkivanov/decompose/blob/main/src/commonMain/kotlin/com/arkivanov/sample/shared/Url.kt), the `Url` data class separates path segments from query parameters:

```kotlin
data class Url(
    val pathSegments: List<String>, 
    val parameters: Map<String, String>
)

```

This lightweight representation allows components to consume path segments sequentially using `consumePathSegment()`, which returns the first segment and a new `Url` with the remaining segments for child components.

## Inject Deep Links at the Entry Point

For web targets, deep links arrive through the browser's address bar. In [`src/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt`](https://github.com/arkivanov/decompose/blob/main/src/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt), the `withWebHistory` function captures the initial URL and passes it to your root component:

```kotlin
@OptIn(ExperimentalDecomposeApi::class)
fun main() {
    val lifecycle = LifecycleRegistry()

    val root = withWebHistory { _, deepLink ->
        DefaultRootComponent(
            componentContext = DefaultComponentContext(
                lifecycle = lifecycle, 
                stateKeeper = stateKeeper
            ),
            featureInstaller = DefaultFeatureInstaller,
            deepLinkUrl = deepLink?.let(::Url)  // Parse raw string to Url
        )
    }
    // ...
}

```

The `deepLink` parameter contains the raw URL string from the browser, which you convert to your `Url` type before injection.

## Build the Initial Navigation Stack

The root component interprets the `Url` to determine which screens should be active. In [`src/commonMain/kotlin/com/arkivanov/sample/shared/root/DefaultRootComponent.kt`](https://github.com/arkivanov/decompose/blob/main/src/commonMain/kotlin/com/arkivanov/sample/shared/root/DefaultRootComponent.kt), the `getInitialStack` method maps path segments to configuration objects:

```kotlin
private fun getInitialStack(deepLinkUrl: Url?): List<Config> {
    val (path, childUrl) = deepLinkUrl?.consumePathSegment() 
        ?: return listOf(Config.Tabs())
    
    return when (path) {
        pathSegmentOf<Config.DynamicFeatures>() -> 
            listOf(Config.Tabs(), Config.DynamicFeatures)
        pathSegmentOf<Config.CustomNavigation>() -> 
            listOf(Config.Tabs(), Config.CustomNavigation)
        pathSegmentOf<Config.Pages>() -> 
            listOf(Config.Tabs(), Config.Pages(deepLinkUrl = childUrl))
        pathSegmentOf<Config.SharedTransitions>() -> 
            listOf(Config.Tabs(), Config.SharedTransitions(deepLinkUrl = childUrl))
        else -> listOf(Config.Tabs(deepLinkUrl = childUrl))
    }
}

```

This approach supports **nested deep linking** by passing the remaining `childUrl` to child components that accept their own `deepLinkUrl` parameters.

## Handle Nested Deep Linking in Child Components

Child stacks can implement the same pattern to handle their own path segments. In [`src/commonMain/kotlin/com/arkivanov/sample/shared/tabs/DefaultTabsComponent.kt`](https://github.com/arkivanov/decompose/blob/main/src/commonMain/kotlin/com/arkivanov/sample/shared/tabs/DefaultTabsComponent.kt), the component extracts its segment and passes the remainder to its own children:

```kotlin
private fun getInitialConfig(deepLinkUrl: Url?): Config {
    val (path, childUrl) = deepLinkUrl?.consumePathSegment() 
        ?: return Config.Menu
    
    return when (path) {
        pathSegmentOf<Config.Counters>() -> Config.Counters
        pathSegmentOf<Config.Cards>() -> Config.Cards
        pathSegmentOf<Config.MultiPane>() -> Config.MultiPane(deepLinkUrl = childUrl)
        else -> Config.Menu
    }
}

```

This recursive pattern allows URLs like `/tabs/multi_pane/123` to navigate through multiple levels of the component hierarchy.

## Synchronize with Browser History

To enable two-way synchronization between your navigation stack and the browser's address bar, expose a `WebNavigation` implementation using `childStackWebNavigation`. In [`DefaultRootComponent.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultRootComponent.kt):

```kotlin
override val webNavigation: WebNavigation<*> = childStackWebNavigation(
    navigator = nav,
    stack = _stack,
    serializer = Config.serializer(),
    pathMapper = { it.configuration.path() },  // Config -> "/pages/123"
    childSelector = {
        when (val child = it.instance) {
            is TabsChild -> child.component
            is PagesChild -> child.component
            is SharedTransitionsChild -> child.component
            else -> null
        }
    }
)

```

The **pathMapper** converts configurations to URL paths, while **childSelector** identifies which child component should handle web navigation for a given stack entry.

## Summary

- **Parse URLs** using a lightweight `Url` data class that separates path segments from query parameters.
- **Inject deep links** at the platform entry point (e.g., `withWebHistory` for web) and pass them to your root component.
- **Build initial stacks** by consuming path segments sequentially with `consumePathSegment()`, mapping each segment to navigation configurations.
- **Support nesting** by passing remaining URL segments to child components that implement their own deep link handling.
- **Enable browser sync** by exposing `WebNavigation` via `childStackWebNavigation` with appropriate `pathMapper` and `childSelector` functions.

## Frequently Asked Questions

### How does Decompose handle query parameters in deep links?

The `Url` data class stores query parameters in a `Map<String, String>`. After parsing the raw URL, components can access these parameters via `url.parameters` to configure initial state, such as pre-filling search fields or setting filter options.

### Can deep linking work on mobile platforms (iOS/Android) as well?

Yes. While the examples show `withWebHistory` for web, mobile platforms receive deep links through platform-specific APIs (e.g., Android Intents or iOS Universal Links). You parse the incoming URL string into your `Url` type and pass it to the root component exactly as shown in the web example.

### What happens if a deep link path doesn't match any configuration?

If `consumePathSegment()` returns a path that doesn't match known configurations, you should fall back to a default configuration (typically the root or home screen). In the sample code, the `else` branch returns `Config.Tabs()` or `Config.Menu` to ensure the app always launches into a valid state.

### Is it possible to deep link into multiple nested stacks simultaneously?

Yes. The pattern supports arbitrary nesting depth. Each component consumes its own path segment and passes the remaining `childUrl` to the next level. For example, a URL `/tabs/multi_pane/123` would activate the Tabs stack, select the MultiPane tab, and pass `123` to the MultiPane component's own navigation stack.