Someone in the LearnDash Facebook group asked how to create a submenu populated with enrolled courses. So I built it for fun:

The enrolled courses menu in action.

This is one of those things that is difficult to write a “LearnDash tutorial” about, because this is just as much of a WordPress theme customization question than it is a LearnDash question.

How would you inject any sort of content into the menu of your theme?

What if you wanted a submenu of “Products Purchased” from WooCommerce?

What if you just wanted to inject a series of static links into a submenu, and didn’t feel like using the built-in interface?

Pro Tip: Themes can do uncommon things with menus. They could be extending the Walker class. They could be using Timber.

So while it would be ideal if we handled all the menu building and rendering on the server side, I thought of a way to leverage JavaScript that should make it far easier to implement on any theme.

If you’re cool with that, read on.

Step #1: Get a theme that supports submenus

While all of my Themetry themes support submenus, themes can do pretty much whatever they want, including not support menus at all.

We’re going to assume that you have a theme that already supports submenus. If yours doesn’t, consider switching themes, or hire a developer to help build it for.

Pretty much any well-made and modern theme will support submenus, so if your publicly-released theme doesn’t then I would heavily lean toward switching because that seems like a gigantor red flag to me.

Step #2: Create a menu item as you normally do

Make the menu item structure like this:

  • Label: Courses. URL: Whatever. Could be # or your course archive page.
    • Label: ERROR ERROR. URL: #enrolled-courses

Or see the following screenshot for an illustration.

Set up your “Enrolled Courses” menu like this.

The labels don’t matter much. I made the second label “ERROR ERROR” because no user should actually see that.

What really matters is the URL of the submenu item equals exactly the following: #enrolled-courses

That is because we will be using JavaScript to specifically target that item to find our where we should be injecting enrolled courses.

Step #3: Add the snippet

Some customization may be required. See the “POTENTIAL CUSTOMIZATION” comments in the JavaScript for more information.

add_action( 'wp_footer', 'lmscoder_enrolled_courses_dropdown' );
function lmscoder_enrolled_courses_dropdown() {
	// Go away if not logged in
	if ( ! is_user_logged_in() ) {
		return;
	}
	
	// Grab enrolled courses
	$enrolled_course_ids = learndash_user_get_enrolled_courses( get_current_user_id() );

	// Go away if no enrolled courses
	if ( ! $enrolled_course_ids ) {
		return;
	}

	// Build menu list array
	$menu_list = [];

	foreach ( $enrolled_course_ids as $id ) {
		$menu_list[] = [
			'url' => get_permalink( $id ),
			'title' => get_the_title( $id ),
		];
	}
	
	// JSONify it for next step
	$menu_list_json = json_encode( $menu_list );
?>
	<script>
		// Convert to JavaScript object
		const menu_obj = JSON.parse( '<?php echo $menu_list_json; ?>' );
		
		// POTENTIAL CUSTOMIZATION: ensure li and a classes match theme
		const menu_html = menu_obj.map( item => `
			<li class="menu-item">
				<a class="menu-link" href="${ item.url }">${ item.title }</a>
			</li>
		` ).join( '' );
		
		// This should not change if you follow tut
		const beacon = document.querySelector( 'a[href="#enrolled-courses"]' );
		
		// POTENTIAL CUSTOMIZATION: Select the closest submenu element.
		const submenu = beacon.closest( '.sub-menu' );
		
		// Destroy the beacon
		beacon.remove();
		
		// Add enrolled courses to submenu
		submenu.insertAdjacentHTML( 'beforeend', menu_html );
	</script>
<?php
}

I happened to use the Astra theme when writing this snippet, so if you’re using that, you should be set.

If you’re not, and the snippet does not work as-is, there’s really no way around this other than to inspect your theme and see what CSS classes are used to style submenu items.

Inspecting submenu links in Astra. You want to make sure you copy the CSS classes styled.

What if the user is logged out, or not enrolled in courses?

Okay, well the function will stop if that’s the case. But the “ERROR ERROR” menu item will remain. Not very professional!

Here’s a way to just get rid of the whole menu item:

add_action( 'wp_footer', 'lmscoder_enrolled_courses_menu_destroyer' );
function lmscoder_enrolled_courses_menu_destroyer() {
	// Go away if logged in
	if ( is_user_logged_in() ) {
		return;
	}
	
	// Grab enrolled courses
	$enrolled_course_ids = learndash_user_get_enrolled_courses( get_current_user_id() );

	// Go away if there are enrolled courses
	if ( $enrolled_course_ids ) {
		return;
	}
?>
	<script>
		function lmscoderEnrolledCoursesMenuDestroyer() {
			// POTENTIAL CUSTOMIZATION: Adjust with actual menu item ID
			const unwanted_menu = document.querySelector( '#menu-item-28673' );

			if ( ! unwanted_menu ) {
				return;
			}

			unwanted_menu.remove();	
		}
		
		lmscoderEnrolledCoursesMenuDestroyer();
	</script>
<?php }

Assuming your menu item ID is not also 28673, that will need to be customized. Once again, no way around this other than inspecting your site.

Inspecting the menu item ID

With this snippet, we could traverse up from that #enrolled-courses link again. Although since you may need to inspect to find the appropriate “parent menu item” selector anyway, I figured it would be easier to just select the menu item ID directly.

Something like Conditional Menus might help to at least check if a user is logged in or not, although it may not account for a logged in user that is not enrolled in any courses.

We want to make sure that “ERROR ERROR” menu item never appears. 🙂