Storyblok Raises $80M Series C - Read News

Skip to main content

Create Dynamic Menus in Storyblok and SvelteKit

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.


On May 13th, 2024, Storyblok started gradually rolling out a new design for its Visual Editor. Therefore, the Visual Editor product screenshots depicted in this resource may not match what you encounter in the Storyblok App. For more information and a detailed reference, please consult this FAQ on the new Visual Editor design.

In this part of the tutorial series, we will make the menu in our header component dynamic, so that you can manage it directly from Storyblok.


If you’re in a hurry, have a look at the code on Github


This tutorial is part 3 of the Ultimate Tutorial Series for SvelteKit. We recommend that you follow the previous tutorials before starting this one.

Setup in Storyblok

First, we will have to create a new content type component wherein our menu entries can be stored. In order to do that, go to the Block Library {1} and create a New block {2}.

Creating a new block in the Block Library

Creating a new block in the Block Library

Enter the name config {1} and choose Content type block {2}.

Creating a content type block

Creating a content type block

Now you can create a new field with the name header_menu {1} and choose the field type Blocks {2}.

Creating a field for the header menu

Creating a field for the header menu

In this field, we would like to provide the possibility to add menu links as nested blocks. To accomplish that, let’s create another new block. This time it should be a Nested block {1} with the name menu_link {2}.

Creating a nested block

Creating a nested block

Now we can add a new field called link {1} in this newly created block and choose Link as the field type {2}.

Creating a link field

Creating a link field

Alright, our component schemas are almost done! Just one more step: to avoid that just any block could be nested in our header_menu, we want to ensure that only specific components can be inserted {1}. Now you can choose the menu_link block in the whitelist {2}.

With that out of the way we can now go to the Content of our Storyblok space. In here, we want to create a new story {1} with the name Config {2}, using our recently created content type Config {3}.

Allowing only specific components to be inserted

If you open this newly created Config story, you can now nest as many menu_link blocks in the header_menu field as you would like. For now, let’s add our Blog and About page.

Dynamic menu rendered correctly in SvelteKit

Dynamic menu rendered correctly in SvelteKit

Rendering the Menu in SvelteKit

Having taken care of our setup in Storyblok, we can now turn to the code and implement our dynamic menu in the frontend. To accomplish that, let’s update components/Header.svelte with the following code:

	export let header;


<header class="w-full h-24 bg-[#f7f6fd]">
    <div class="container h-full mx-auto flex items-center justify-between">
        <a href="home">
            <h1 class="text-[#50b0ae] text-3xl font-bold">Storyblok SvelteKit</h1>
        {#if header}
                <ul class="flex space-x-8 text-lg font-bold">
                    {#each header as blok}
                        <li class="hover:text-[#50b0ae]">
                            <a href={}> {}</a>

We also need to adjust the +layout.js: here, we'll need to create a dataConfig variable in which we can store the data we get using useStoryblokApi from the config Story and pass the version as well as the resolve_links parameter. Then, we'll return the header_menu (with a property named header) as well as the storyblokApi.

      import Feature from "../components/Feature.svelte";
import Grid from "../components/Grid.svelte";
import Page from "../components/Page.svelte";
import Teaser from "../components/Teaser.svelte";
import { apiPlugin, storyblokInit, useStoryblokApi } from "@storyblok/svelte";

/** @type {import('./$types').LayoutLoad} */
export async function load() {
    accessToken: "U4mv54ozRXmfv2EraUpiTwtt",
    use: [apiPlugin],
    components: {
      feature: Feature,
      grid: Grid,
      page: Page,
      teaser: Teaser,
  let storyblokApi = await useStoryblokApi();
+  const dataConfig = await storyblokApi.get('cdn/stories/config/', {
+    version: 'draft',
+    resolve_links: 'url'
+  });

  return {
    storyblokApi: storyblokApi,
+    header:


Returning the header in your +layout.js file, now in your +layout.svelte file, you can access the header property, declaring the data variable (that will include data.header):

	import '../app.css';
	import Header from '../components/Header.svelte';
+	export let data;


+	<Header header={data.header} />
	<slot />

When you call the Header component (line 9), you can share with the component also the data.header.

If you go back to the Visual Editor now, you can see your menu rendered correctly. Feel free to experiment with it by adding or reordering the entries and saving the Config story.

Fantastic – but what’s actually happening in the code? First of all, we’re using useStoryblokApi to fetch the Config story without loading the Storyblok Bridge automatically. You can learn more about the differences between useStoryblok, useStoryblokApi and useStoryblokBridge in the documentation on Github. What’s important to notice is that an additional parameter – resolve_links – is passed to the apiOptions. This is used to get the URLs of the stories we link internally. You can learn more about this and other parameters in our Content Delivery API docs.

Next, we store the contents of our header_menu field in the reactive headerMenu object which we create at the top. This can then be used for a simple loop, displaying all our menu items. 

Wrapping Up

Congrats, you now have successfully created a dynamic menu in Storyblok and SvelteKit - great job! 🎉

Next Part:

Continue reading to learn How to Create Custom Components in Storyblok & SvelteKit.


Josefine Schaefer

Josefine Schaefer

Josefine is a frontend engineer with a passion for JavaScript, web accessibility and community-building. Before entering the tech industry, she worked as a communications specialist - nowadays, she combines these skills with her curiosity for technology and works as a Developer Relations Engineer at Storyblok.