Skip to main content

Create Dynamic Menus in Storyblok and Laravel

Contents
    Try Storyblok

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

    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.

    GitHub source code:

    If you’re in a hurry, have a look at our source code demo in GitHub!

    Requirements

    This tutorial is the third part of the Ultimate Tutorial Series for Laravel. 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}.

    app.storyblok.com
    Creating a new block in the Block Library
    1
    2

    Creating a new block in the Block Library

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

    app.storyblok.com
    Creating a content type block
    1
    2

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

    app.storyblok.com
    Creating a field for the header menu
    1
    2

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

    app.storyblok.com
    Creating a nested block
    1
    2

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

    app.storyblok.com
    Creating a link field
    1
    2

    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 make sure that only specific components can be inserted {1}. Now you can choose the menu_link block in the whitelist {2}.

    app.storyblok.com
    Allowing only specific components to be inserted
    1
    2

    Allowing only specific components to be inserted

    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 with the name Config {2}, using our recently created content type Config {3}.

    app.storyblok.com
    Creating a new Config story
    1
    2

    Creating a new Config story

    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.

    app.storyblok.com
    Dynamic menu rendered correctly in Laravel

    Dynamic menu rendered correctly in Laravel

    Rendering the Menu in Laravel

    Having taken care of our setup in Storyblok, we can now turn to the code and implement our dynamic menu in the blade template. To accomplish that, we have to refactor a bit of the code we already implemented for the static header, and add the dynamic Header component. In detail we will:

    • add a new component class

    • add the logic for retrieving the new config Story (via the Storyblok PHP SDK) in a new component class

    • refactor the component blade template and make it dynamic

    The Header component class

    We are going to create a component class for implementing the logic for retrieving the config Story from Storyblok. Create a new file app/View/Components/Header.php. We will implement the two methods: __construct() and render():

    app/View/Components/Header.php
    <?php
    
    namespace App\View\Components;
    
    use Illuminate\Support\Arr;
    use Illuminate\View\Component;
    
    class Header extends Component
    {
        public $headerMenu;
    
        /**
         * Create a new component instance.
         *
         * @return void
         */
        public function __construct()
        {
            $storyblok = new \Storyblok\Client(config('storyblok.api_key'));
            $storyblok->editMode();
            $storyblok->resolveLinks('url');
            // retrieving "config" Story
            $response = $storyblok->getStoryBySlug('config');
            $data = $response->getBody();
            $this->headerMenu = Arr::get($data, 'story.content.header_menu', []);
        }
    
        /**
         * Get the view / contents that represent the component.
         *
         * @return \Illuminate\View\View|\Closure|string
         */
        public function render()
        {
            return view('components.common.header');
        }
    }
    

    In the constructor method we are going to create an instance of Storyblok Client, using the API key from the configuration, setting the editMode (for retrieving the draft version), and setting the resolve links for the URL. For resolving the links we are using: $storyblok->resolveLinks('url');.

    If your space was created in the United States, don't forget to set the apiRegion parameter in Storyblok\Client initialization. If you need more examples, you can see the PHP SDK readme documentation for "Initialization for US spaces"

    In the constructor we are retrieving the information from a specific Story ('config'): $response = $storyblok->getStoryBySlug('config');.

    In the render() method we are going to load the blade in resources/views/components/common/header.blade.php via the view() helper: return view('components.common.header');

    The Header blade template

    In the file resources/views/components/common/header.blade.php already created in the Part 2 of the Laravel Ultimate Tutorial series, we can load dynamically the menu items thanks to the work made in the component class that filled the $headerMenu variable. The $headerMenu variable is an array, where we can find the menu items, and for each menu item, we can access the full_slug property for composing the destination URL and the name for the label of the link.

    resources/views/components/common/header.blade.php
    <header class="w-full h-24 bg-[#f7f6fd]">
      <div class="container h-full mx-auto flex items-center justify-between">
        <a href="{{url('/')}}" <h1 class="text-[#50b0ae] text-3xl font-bold">Storyblok &#10084; Laravel</h1>
        </a>
        <nav>
          <ul class="flex space-x-8 text-lg font-bold">
            @foreach ($headerMenu as $menuItem)
            <li>
              <x-common.menu_item link="{{url(
                Arr::get($menuItem, 'link.story.full_slug', '/')
              )}}" label="{{Arr::get($menuItem, 'link.story.name', '')}}" />
            </li>
            @endforeach
          </ul>
        </nav>
      </div>
    </header>

    Now, we can refactor the way we are calling the Header component from the blade templates. Let's remove the header in the layout file:

    resources/views/components/common/layout.blade.php
    <body class="antialiased">
    -  <x-common.header></x-common.header>
      {{ $slot }}
      <script type="text/javascript" src="//app.storyblok.com/f/storyblok-v2-latest.js"></script>
      <script type="text/javascript">

    Afterwards, we can include the new header here:

    resources/views/components/page.blade.php
    +<x-header></x-header>
    {!! Arr::get($blok, '_editable', '') !!}
    <div class="font-[Roboto] px-4">
        @foreach ($blok['body'] as $blok)
            <x-dynamic-component :component="$blok['component']" :blok="$blok" class="mt-4" />
        @endforeach
    </div>

    And finally create the resources/views/components/config.blade.php file, for allowing the correct rendering when the editor user will select the config Story in the visual editor:

    resources/views/components/config.blade.php
    <x-header></x-header>

    When opening the visual editor, you can now see the expected result:

    Your Laravel application renders the dynamic menu

    Your Laravel application renders the dynamic menu

    Please remember to start your Laravel application locally via php artisan server. Since HTTPS is a requirement for the Visual Editor, you also have to use the following command (as explained in Part 1):

    local-ssl-proxy --hostname 127.0.0.1 --source 8010 --target 8000 --cert localhost.pem --key localhost-key.pem

    5 min tutorial:

    If you missed something, you can start reading and learn Add a headless CMS to Laravel in 5 minutes.

    Developer Newsletter

    Want to stay on top of the latest news and updates in Storyblok?
    Subscribe to Code & Bloks - our headless newsletter.

    An error occurred. Please get in touch with marketing@storyblok.com

    Please select at least one option.

    Please enter a valid email address.

    This email address is already registered.

    Please Check Your Email!

    Almost there! To confirm your subscription, please click on the link in the email we’ve just sent you. If you didn’t receive the email check your ’junk folder’ or