Skip to content

Proper Solution: Using Apex + LWC

Step 1: Create Apex Controller

NavigationMenuController.cls

apex

public with sharing class NavigationMenuController {
    
    @AuraEnabled(cacheable=true)
    public static List<NavigationMenuItemWrapper> getNavigationMenuItems(String menuName) {
        List<NavigationMenuItemWrapper> menuItemsList = new List<NavigationMenuItemWrapper>();
        
        try {
            // Get the current site's Network ID
            String networkId = Network.getNetworkId();
            
            // Query NavigationLinkSet to get the menu
            List<NavigationLinkSet> navLinkSets = [
                SELECT Id, MasterLabel 
                FROM NavigationLinkSet 
                WHERE MasterLabel = :menuName 
                AND NetworkId = :networkId
                LIMIT 1
            ];
            
            if (navLinkSets.isEmpty()) {
                return menuItemsList;
            }
            
            // Query NavigationMenuItem records
            List<NavigationMenuItem> menuItems = [
                SELECT Id, Label, Target, Type, Position, 
                       DefaultListViewId, AccessRestriction, ParentId
                FROM NavigationMenuItem 
                WHERE NavigationLinkSetId = :navLinkSets[0].Id
                ORDER BY Position ASC
            ];
            
            // Convert to wrapper objects
            for (NavigationMenuItem item : menuItems) {
                NavigationMenuItemWrapper wrapper = new NavigationMenuItemWrapper();
                wrapper.id = item.Id;
                wrapper.label = item.Label;
                wrapper.target = item.Target;
                wrapper.type = item.Type;
                wrapper.defaultListViewId = item.DefaultListViewId;
                wrapper.accessRestriction = item.AccessRestriction;
                wrapper.parentId = item.ParentId;
                
                menuItemsList.add(wrapper);
            }
            
        } catch (Exception e) {
            throw new AuraHandledException('Error fetching navigation: ' + e.getMessage());
        }
        
        return menuItemsList;
    }
    
    // Wrapper class to return navigation items
    public class NavigationMenuItemWrapper {
        @AuraEnabled public String id;
        @AuraEnabled public String label;
        @AuraEnabled public String target;
        @AuraEnabled public String type;
        @AuraEnabled public String defaultListViewId;
        @AuraEnabled public String accessRestriction;
        @AuraEnabled public String parentId;
    }
}

Step 2: Create LWC Component

customNavigation.js

javascript

import { LightningElement, wire } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import getNavigationMenuItems from '@salesforce/apex/NavigationMenuController.getNavigationMenuItems';

export default class CustomNavigation extends NavigationMixin(LightningElement) {
    navItems = [];
    error;
    isLoading = true;

    // Wire the Apex method
    @wire(getNavigationMenuItems, { menuName: 'Default_Navigation' })
    wiredNavItems({ error, data }) {
        this.isLoading = false;
        if (data) {
            this.navItems = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.navItems = [];
            console.error('Navigation error:', error);
        }
    }

    handleNavigation(event) {
        event.preventDefault();
        
        const menuItemId = event.currentTarget.dataset.id;
        const menuItem = this.navItems.find(item => item.id === menuItemId);
        
        if (!menuItem) return;

        // Handle different navigation types
        let navigationConfig = this.buildNavigationConfig(menuItem);
        
        this[NavigationMixin.Navigate](navigationConfig);
    }

    buildNavigationConfig(menuItem) {
        // Handle different menu item types
        switch(menuItem.type) {
            case 'SalesforceObject':
                return {
                    type: 'standard__objectPage',
                    attributes: {
                        objectApiName: menuItem.target,
                        actionName: 'home'
                    }
                };
            
            case 'InternalLink':
                return {
                    type: 'comm__namedPage',
                    attributes: {
                        name: menuItem.target
                    }
                };
            
            case 'ExternalLink':
                return {
                    type: 'standard__webPage',
                    attributes: {
                        url: menuItem.target
                    }
                };
            
            case 'Event':
                // Handle custom event if needed
                return null;
            
            default:
                // Default to named page
                return {
                    type: 'comm__namedPage',
                    attributes: {
                        name: menuItem.target
                    }
                };
        }
    }

    get hasNavItems() {
        return this.navItems && this.navItems.length > 0;
    }
}

customNavigation.html

html

<template>
    <nav class="nav-container">
        <!-- Loading State -->
        <template if:true={isLoading}>
            <div class="loading">Loading navigation...</div>
        </template>

        <!-- Navigation Items -->
        <template if:true={hasNavItems}>
            <ul class="nav-list">
                <template for:each={navItems} for:item="item">
                    <li key={item.id} class="nav-item">
                        <a 
                            href="javascript:void(0);" 
                            data-id={item.id}
                            onclick={handleNavigation}
                            class="nav-link">
                            {item.label}
                        </a>
                    </li>
                </template>
            </ul>
        </template>

        <!-- Error State -->
        <template if:true={error}>
            <div class="error-message">
                <p>Unable to load navigation menu</p>
                <p class="error-detail">{error.body.message}</p>
            </div>
        </template>
    </nav>
</template>

customNavigation.css

css

.nav-container {
    background-color: #f3f3f3;
    padding: 1rem;
}

.nav-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
}

.nav-item {
    display: inline-block;
}

.nav-link {
    color: #0176d3;
    text-decoration: none;
    padding: 0.5rem 1rem;
    display: block;
    font-weight: 500;
    transition: all 0.2s ease;
}

.nav-link:hover {
    color: #014486;
    background-color: #e5e5e5;
    border-radius: 4px;
}

.loading {
    text-align: center;
    padding: 1rem;
    color: #706e6b;
}

.error-message {
    background-color: #feded8;
    border-left: 4px solid #c23934;
    padding: 1rem;
    color: #3e3e3c;
}

.error-detail {
    font-size: 0.875rem;
    margin-top: 0.5rem;
    color: #706e6b;
}

customNavigation.js-meta.xml

xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>60.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightningCommunity__Default">
            <property name="menuName" type="String" default="Default_Navigation" 
                      label="Navigation Menu Name" 
                      description="API Name of the navigation menu to display"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Setup Instructions:

  1. Find Your Navigation Menu API Name:
    • Go to SetupDigital ExperiencesSettingsNavigation Menus
    • Find your menu and note the API name (e.g., Default_Navigation)
  2. Deploy the Apex class with test coverage
  3. Deploy the LWC component
  4. Add to Experience Builder:
    • Drag the component onto your page
    • Configure the menuName property to match your navigation menu

This solution properly queries the NavigationMenuItem object through Apex and handles different navigation types correctly!