New Wagtail Course! 🥳 The Ultimate Wagtail Developers Course

Tutorial Wagtail Version: 2.x

How to Create a Custom Wagtail Menu System

Almost every website has some form of navigation. Wagtail websites are no different. But creating a menu isn't as easy as making top level pages (although that's an option!). In this tutorial we're going to explore how to create a Menu System using a Clusterable Model, an Oderable, a Snippet, and a custom template tag.. from scratch!

Every good site has a navigation, and it's usually found in the header.

Wagtail already comes with an option to show pages in your menus, and all you have to do is query pages using

But that doesn't give you control over the placement of each page. So let's say you wanted a contact button to the very right of your menu, that's gets harder to do. You can drag and drop pages to have the proper order, and that would solve the ordering problem.

But what if you want to link to:

  • A certain part of a page, or
  • An external URL

Well then you're kind of stuck if you want either of those options. Also creating a mega menu would be pretty difficult.

Luckily Wagtail gives us all the ingredients we need to make our own custom menu system using:

  • 1 Clusterable Model
  • 1 Orderable Model
  • 1 Snippet
  • 1 Template Tag
  • and 1 loop in our template

Below you'll find the code that was used in the video.

Note: there are different files, and they all belong to an app called "menus".

``` """menus/templatetags/""" from django import template from ..models import Menu register = template.Library() @register.simple_tag() def get_menu(slug): return Menu.objects.filter(slug=slug).first() ```
``` """menus/""" from django.db import models from django_extensions.db.fields import AutoSlugField from modelcluster.fields import ParentalKey from modelcluster.models import ClusterableModel from wagtail.admin.edit_handlers import ( MultiFieldPanel, InlinePanel, FieldPanel, PageChooserPanel, ) from wagtail.core.models import Orderable from wagtail.snippets.models import register_snippet class MenuItem(Orderable): link_title = models.CharField( blank=True, null=True, max_length=50 ) link_url = models.CharField( max_length=500, blank=True ) link_page = models.ForeignKey( "wagtailcore.Page", null=True, blank=True, related_name="+", on_delete=models.CASCADE, ) open_in_new_tab = models.BooleanField(default=False, blank=True) page = ParentalKey("Menu", related_name="menu_items") panels = [ FieldPanel("link_title"), FieldPanel("link_url"), PageChooserPanel("link_page"), FieldPanel("open_in_new_tab"), ] @property def link(self): if self.link_page: return self.link_page.url elif self.link_url: return self.link_url return '#' @property def title(self): if self.link_page and not self.link_title: return self.link_page.title elif self.link_title: return self.link_title return 'Missing Title' @register_snippet class Menu(ClusterableModel): """The main menu clusterable model.""" title = models.CharField(max_length=100) slug = AutoSlugField(populate_from="title", editable=True) # slug = models.SlugField() panels = [ MultiFieldPanel([ FieldPanel("title"), FieldPanel("slug"), ], heading="Menu"), InlinePanel("menu_items", label="Menu Item") ] def __str__(self): return self.title ```
``` {% load menus_tags %} {# Your template; perhaps base.html #} {% get_menu "main" as navigation %} {% for item in navigation.menu_items.all %} <a href="{{ }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a> {% endfor %} ```
Wagtailmenus Repo

Near the end of this video I briefly talk about a package called wagtailmenus that basically does all of this for you (and a little more). Here are the link:

Related tutorials

The Ultimate Wagtail Developers Course

This course covers everything from basic installation to advanced features like custom blocks and API integration, it's perfect for developers looking to enhance their skills with this powerful CMS.

Ultimate Wagtail Developers Course Logo