Skip to main content
Our docs redesign is live!

Modeling navigation in Contentstack

8 min read

This guide shows how to model navigation in Contentstack using content types, references, Global Fields, and Modular Blocks. You will build three navigation models that build on each other:

  • Simple navigation: internal and external links

  • Drop-down navigation: internal and external links under a parent item

  • Mega menu: internal and external links, nested drop-down items, plus optional CTAs and images per item (not per column)

When to use it

Use these patterns when:

  • Marketing and product teams need to manage navigation without deploys

  • Navigation must link to multiple internal page types (for example Page, Landing Page)

  • You need predictable front-end rendering while keeping authoring simple

Prerequisites

  • A Contentstack stack with access to Content Types, Global Fields, and Modular Blocks

  • At least two internal page content types (examples used below): Page and Landing Page

  • A front end that can resolve referenced entries to URLs (via a url/slug field, or your routing layer)

How it works

The core problem is modeling a “link” that can point to either:

  • An internal entry (one of your page-like content types)

  • An external URL

In Contentstack, you typically solve this by creating a reusable Link Global Field using a Modular Block with two mutually exclusive options: Internal and External. Navigation items then compose these links and add structure (drop-downs, mega menu content) around them.

Assumptions used in this guide

  • Internal destinations are references to specific page content types (for example Page and Landing Page).

  • Each page type exposes a URL field (for example url or slug) that the front end can use.

  • Navigation is modeled as a dedicated content type so it can be versioned, localized, and referenced by pages/layouts.

Step by step

Step 1 — Create a reusable Link Global Field

Create a Global Field named link. This will be reused across all navigation use cases.

Global Field: link

  • label (Single line text, required)

  • link (Modular Blocks, required) with blocks:

  • internal

    • entry (Reference, required; limit to: Page, Landing Page)

    • open_in_new_tab (Boolean, default false)

  • external

    • url (Single line text, required)

    • open_in_new_tab (Boolean, default true)

Authoring rule: allow exactly one block instance in link. This makes it explicit that a link is either internal or external.

Common pitfalls

  • Allowing multiple blocks in the Link field. If authors can add both internal and external, your rendering logic becomes ambiguous.

  • Not restricting reference content types. Keep internal links constrained to your page-like types to avoid “linking to anything”.

Use case 1 — Simple navigation (internal + external links)

What this does

Models a flat list of navigation items, where each item is a link (internal or external).

When to use it

  • Top bars and footers with one level of links

  • Early project phase where you want a stable, minimal model

Content Type: navigation_simple

  • title (Single line text, required)

  • items (Group, multiple, required)

Group: items[]

  • item (Global Field: link, required)

How it works

items[] is an ordered list. The front end renders each item as a single anchor based on whether the link is internal or external.

Example entry shape (simplified)

{
  "title": "Header nav",
  "items": [
    {
      "item": {
        "label": "Home",
        "link": [{ "internal": { "entry": { "uid": "home" }, "open_in_new_tab": false } }]
      }
    },
    {
      "item": {
        "label": "Docs",
        "link": [{ "external": { "url": "https://docs.example.com", "open_in_new_tab": true } }]
      }
    }
  ]
}

Common pitfalls

  • Missing ordering requirements. Confirm the field preserves order (Groups do) and render in that order.

  • Assuming referenced entries include URL by default. Your delivery query needs to include the referenced fields required to build URLs.

Next steps

Once flat navigation is stable, add drop-down behavior by allowing a parent item to optionally contain child links.

Use case 2 — Drop-down navigation (internal + external links)

What this does

Models top-level navigation items that can either be a direct link or a parent item containing a drop-down list of links.

When to use it

  • Header navigation with one drop-down level

  • Information architecture where top-level categories expand into child links

Approach

Use a single nav item structure where each top-level item can be one of:

  • A direct link

  • A drop-down with child links

Global Field: nav_item_dropdown

Create a Global Field named nav_item_dropdown using a Modular Block. This keeps the entry shape explicit and avoids optional-field ambiguity.

  • item (Modular Blocks, required; allow exactly one block) with blocks:

  • link_item

    • link (Global Field: link, required)

  • dropdown_item

    • label (Single line text, required)

    • children (Group, multiple, required)

Group: dropdown_item.children[]

  • link (Global Field: link, required)

Content Type: navigation_dropdown

  • title (Single line text, required)

  • items (Group, multiple, required)

Group: items[]

  • item (Global Field: nav_item_dropdown, required)

How it works

  • link_item renders as a standard navigation link.

  • dropdown_item renders as a label (or button) that expands into children[].

Example entry shape (simplified)

{
  "title": "Header nav",
  "items": [
    {
      "item": {
        "item": [
          { "link_item": { "link": { "label": "Pricing", "link": [{ "internal": { "entry": { "uid": "pricing" } } }] } } }
        ]
      }
    },
    {
      "item": {
        "item": [
          {
            "dropdown_item": {
              "label": "Products",
              "children": [
                { "link": { "label": "Product A", "link": [{ "internal": { "entry": { "uid": "product-a" } } }] } },
                { "link": { "label": "API", "link": [{ "external": { "url": "https://api.example.com" } }] } }
              ]
            }
          }
        ]
      }
    }
  ]
}

Common pitfalls

  • Making both a link and children optional in the same object. That produces “sometimes link, sometimes group” logic that breaks validation and authoring.

  • Not defining click behavior for drop-down labels. Decide whether the parent label is clickable or only toggles the menu. If clickable, model it as a link too (see next use case).

Next steps

If you need richer navigation content (promos, CTAs, images) and deeper nesting, introduce a mega menu model that supports multiple item types and optional media per item.

Use case 3 — Mega menu / complex navigation

What this does

Models a navigation system that supports:

  • Internal and external links

  • Drop-downs with nested items

  • Optional CTA and images per item (not per column)

When to use it

  • Large sites where navigation contains promoted items, editorial content, or visual tiles

  • Multiple navigation “item types” in the same menu (link, group, promo)

  • When you need explicit structure to keep rendering deterministic

Approach

Use a single Navigation content type with a list of top-level items. Each item is a nav node with a specific type. Nodes can contain children, enabling nested structures. Promo content (CTA, image) is attached to nodes, not to columns.

Global Field: nav_promo

Create a Global Field named nav_promo:

  • eyebrow (Single line text, optional)

  • description (Single line text or JSON RTE, optional)

  • image (Asset, optional; restrict to images if your governance requires it)

  • cta (Global Field: link, optional)

Global Field: nav_node

Create a Global Field named nav_node using Modular Blocks (required; allow exactly one block). This enforces a single node type per item.

  • node (Modular Blocks) with blocks:

  • link_node

    • link (Global Field: link, required)

    • promo (Global Field: nav_promo, optional)

  • dropdown_node

    • label (Single line text, required)

    • link (Global Field: link, optional; use if the parent label is clickable)

    • items (Group, multiple, required)

    • promo (Global Field: nav_promo, optional)

Group: dropdown_node.items[]

  • item (Global Field: nav_node, required)

This recursion (a node containing a list of nodes) supports deeper nesting while keeping each level typed and predictable.

Content Type: navigation

  • title (Single line text, required)

  • location (Dropdown, required; example values: header, footer)

  • items (Group, multiple, required)

Group: items[]

  • item (Global Field: nav_node, required)

How it works

  • Top-level items[] are rendered in order.

  • Each item is either a link_node or dropdown_node.

  • dropdown_node contains nested items[] of the same nav_node type, allowing multi-level menus.

  • Any node can optionally carry promo (image + CTA + copy). This is per item, not per column.

Example entry shape (simplified)

{
  "title": "Header nav",
  "location": "header",
  "items": [
    {
      "item": {
        "node": [
          {
            "dropdown_node": {
              "label": "Solutions",
              "link": { "label": "All solutions", "link": [{ "internal": { "entry": { "uid": "solutions" } } }] },
              "promo": {
                "eyebrow": "New",
                "description": "See what’s new this quarter",
                "image": { "uid": "asset_uid" },
                "cta": { "label": "Explore", "link": [{ "internal": { "entry": { "uid": "whats-new" } } }] }
              },
              "items": [
                {
                  "item": {
                    "node": [
                      {
                        "link_node": {
                          "link": { "label": "E-commerce", "link": [{ "internal": { "entry": { "uid": "ecommerce" } } }] }
                        }
                      }
                    ]
                  }
                },
                {
                  "item": {
                    "node": [
                      {
                        "dropdown_node": {
                          "label": "Developers",
                          "items": [
                            {
                              "item": {
                                "node": [
                                  {
                                    "link_node": {
                                      "link": { "label": "API Docs", "link": [{ "external": { "url": "https://docs.example.com" } }] }
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}

Common pitfalls

  • Unbounded depth. Recursive models are powerful, but teams often need a maximum depth (for example 2 or 3 levels) to keep UX and accessibility manageable. If you need a hard limit, enforce it in editorial guidelines and front-end validation.

  • Promo content without rendering rules. Define where promos appear (for example right rail, inline tile) and what happens when both an image and CTA are missing.

  • Overusing references for navigation nodes. Keep nodes embedded in the navigation entry unless you truly need node reuse across multiple navigations. Reference-heavy navigation increases query complexity and makes preview/debugging harder.

  • Inconsistent internal URL resolution. Ensure all referenced page types expose the fields your router needs, and your query includes them.

Next steps

  • Define a routing contract for internal pages (for example each page type must have url).

  • Add validation in your front end (or webhook) to enforce maximum depth and required fields per node type.

  • Decide how navigation is selected per locale/site (for example one navigation entry per locale and location).