---
title: Dynamic Navigation in CMP
description: Learn how to enable dynamic rendering of new stories by setting up catch-all navigation in Compose Multiplatform.
url: https://www.storyblok.com/docs/quickstarts/compose-multiplatform/dynamic-routing
---

# Dynamic Navigation in CMP

Use [Navigation 3](https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html) to set up a catch-all navigation strategy in your Compose Multiplatform (CMP) project and render new stories dynamically.

## Add dependencies

In the version catalog TOML file, add the Navigation 3 library.

gradle/libs.versions.toml

```kotlin
[versions]
agp = "9.1.1"
…
storyblok = "0.7.1"
navigation3 = "1.1.1"

[libraries]
…
storyblok-material3 = { module = "com.storyblok:storyblok-material3", version.ref = "storyblok" }
navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
…
```

To add the dependency, update `shared/build.gradle.kts`.

shared/build.gradle.kts

```kotlin
…
sourceSets {
  commonMain.dependencies {
    …
    implementation(libs.storyblok.material3)
    implementation(libs.navigation3.ui)
  }
}
```

Now reload the Gradle project to download the added dependencies and re-analyze the project.

## Create a story navigation key

Create a `StoryKey.kt` file in the common source set to define a navigation key that can represent any story, identified by either its UUID or slug.

shared/src/commonMain/kotlin/org/example/project/StoryKey.kt

```kotlin
package org.example.project

import androidx.navigation3.runtime.NavKey
import androidx.savedstate.serialization.SavedStateConfiguration
import com.storyblok.cdn.schema.Component
import com.storyblok.cdn.schema.Story
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import kotlin.uuid.Uuid

@Serializable
data class StoryKey(
  @Transient
  val story: Story<out Component>? = null,
  @EncodeDefault
  val uuid: Uuid? = story?.uuid,
  @EncodeDefault
  val slug: String? = story?.slug,
) : NavKey

val HomeKey = StoryKey(slug = "home")

val NavConfiguration = SavedStateConfiguration {
  serializersModule = SerializersModule {
    polymorphic(NavKey::class) {
      subclass(StoryKey::class, StoryKey.serializer())
    }
  }
}
```

`StoryKey` is a `NavKey` that persists the `uuid` or `slug` of a story to the back stack. You can also construct a `StoryKey` with a [`Story`](https://storyblok.github.io/storyblok-kotlin/content-api-client/com.storyblok.cdn.schema/-story/index.html) instance for immediate display when navigating between screens.

> [!WARNING]
> On Android, Navigation 3 finds a key’s serializer by reflection. iOS and web have no reflection, so register every `NavKey` as a polymorphic subclass in a `SavedStateConfiguration` and pass it to `rememberNavBackStack`. Without the registration, the app compiles but fails at runtime on iOS and web.

## Display the back stack

In your `App.kt` file, add the code to initialize and use a [`NavDisplay`](https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html) to render the top of the back stack.

shared/src/commonMain/kotlin/org/example/project/App.kt

```kotlin
…
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.remember
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay

@Composable
fun App() {
  MaterialTheme {
    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

      val backStack = rememberNavBackStack(NavConfiguration, HomeKey)

      Storyblok(
        accessToken = "YOUR_ACCESS_TOKEN",
        version = Draft,
        region = EU, // Choose the correct region from your Space.
        …
      ) {
        val story by story("home").collectAsStateWithLifecycle(null)
        story?.content?.let { Block(it, Modifier.padding(innerPadding)) }
        NavDisplay(
          backStack,
          Modifier.padding(innerPadding),
          entryProvider = entryProvider {
            entry<StoryKey> { key ->
              val story by remember(key) {
                when (key.uuid) {
                  null -> story(slug = key.slug!!)
                  else -> story(uuid = key.uuid)
                }
              }.collectAsStateWithLifecycle(key.story)

              story?.content?.let { Block(it) }
            }
          }
        )
      }
    }
  }
}
```

The single `entry<StoryKey>` handles every possible story destination. It fetches the story by UUID or slug, populating the initial value of [`collectAsStateWithLifecycle`](https://developer.android.com/reference/kotlin/androidx/lifecycle/compose/collectAsStateWithLifecycle.composable) with the key’s story instance so available content appears immediately.

> [!TIP]
> To navigate to a story from a block composable, push a `StoryKey` onto the back stack.
> 
> ```kotlin
> backStack.add(StoryKey(story = article)) // from Story instance
> backStack.add(StoryKey(slug = "articles/my-article")) // by slug
> ```

`NavDisplay` also handles the platform’s back gesture: the system back button on Android, and the back swipe on iOS.

> [!NOTE]
> On the web target, the back stack is in-app only: the browser’s back button leaves the app’s back stack unchanged.

With this approach, the project renders any new story in the space as soon as the app pushes its key onto the back stack.

## Related resources

[Content Delivery API: Retrieve a Single Story](/docs/api/content-delivery/v2/stories/retrieve-a-single-story)

[Kotlin: Navigation 3 in Compose Multiplatform](https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html)

[Android Developers: Navigation 3 Overview](https://developer.android.com/guide/navigation/navigation-3)

## Pagination

-   [Previous: Integrate CMP with Storyblok](/docs/quickstarts/compose-multiplatform)
-   [Next: Visual Preview in CMP](/docs/quickstarts/compose-multiplatform/visual-preview)
