add span to top level links of a wordpress menu called “main”

$args, the second parameter passed to wp_nav_menu_objects can be used to check which menu is being processed. This will allow us to target only the desired menu.

In the code below, we check if the slug of the menu is main and if so, wrap the top level menu items in span tags.

/**
 * Filters the sorted list of menu item objects before generating the menu's HTML.
 *
 * @param array    $sorted_menu_items The menu items, sorted by each menu item's menu order.
 * @param stdClass $args              An object containing wp_nav_menu() arguments.
 */
add_filter( 'wp_nav_menu_objects', 'wpse_wp_nav_menu_objects', 10, 2 );
function wpse_wp_nav_menu_objects( $sorted_menu_items, $args  ) {
    // Only modify the "main" menu.
    if ( ! isset( $args->menu->slug ) || 'main' !== $args->menu->slug ) {
        return $sorted_menu_items;
    }

    // Loop over the menu items wrapping only top level items in span tags.
    foreach ( $sorted_menu_items as $item ) {
        if ( ! $item->menu_item_parent ) {
            $item->title="<span>" . $item->title . '</span>';
        }
    }

    return $sorted_menu_items;
}

The filter above will technically run for all of the menus, but with our check in place, it will only affect the desired menu.

Whether or not wp_nav_menu_objects is the best filter is up for debate, but I think it’s a good fit to solve the issue at hand. It’s certainly better than using one of the later filters such as wp_nav_menu_items, wp_nav_menu_{$menu->slug}_items, or wp_nav_menu which would require parsing the menu’s HTML.