Customizing and Previewing Menu Item Fields in WordPress – The Right Way

WordPress provides a robust menu system, but sometimes you need to extend it with custom fields for menu items. Here’s how to do it properly:

1. Adding Custom Fields to Menu Items

Using the wp_nav_menu_item_custom_fields hook (WordPress 5.4+)

add_action('wp_nav_menu_item_custom_fields', 'add_custom_menu_fields', 10, 4);
function add_custom_menu_fields($item_id, $item, $depth, $args) {
    ?>
    <div class="field-custom_field description description-wide">
        <label for="edit-menu-item-custom-<?php echo $item_id; ?>">
            <?php _e('Custom Field'); ?><br />
            <input type="text" id="edit-menu-item-custom-<?php echo $item_id; ?>" 
                   class="widefat code edit-menu-item-custom" 
                   name="menu-item-custom[<?php echo $item_id; ?>]" 
                   value="<?php echo esc_attr($item->custom); ?>" />
        </label>
    </div>
    <?php
}

For older WordPress versions (pre-5.4)

add_filter('wp_edit_nav_menu_walker', 'custom_menu_walker', 10, 2);
function custom_menu_walker($walker, $menu_id) {
    return 'Walker_Nav_Menu_Edit_Custom';
}

// Create a custom walker
require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
class Walker_Nav_Menu_Edit_Custom extends Walker_Nav_Menu_Edit {
    function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        parent::start_el($output, $item, $depth, $args, $id);
        
        // Add your custom fields here
        $custom_field = get_post_meta($item->ID, '_menu_item_custom', true);
        
        $output = str_replace(
            '<fieldset class="field-move hide-if-no-js">',
            '<div class="field-custom description description-wide">
                <label for="edit-menu-item-custom-' . $item->ID . '">
                    ' . __('Custom Field') . '<br />
                    <input type="text" id="edit-menu-item-custom-' . $item->ID . '" 
                           class="widefat code edit-menu-item-custom" 
                           name="menu-item-custom[' . $item->ID . ']" 
                           value="' . esc_attr($custom_field) . '" />
                </label>
            </div>
            <fieldset class="field-move hide-if-no-js">',
            $output
        );
    }
}

2. Saving Custom Fields

 add_action('wp_update_nav_menu_item', 'save_custom_menu_fields', 10, 3);
function save_custom_menu_fields($menu_id, $menu_item_db_id, $args) {
    if (isset($_REQUEST['menu-item-custom'][$menu_item_db_id])) {
        $custom_value = sanitize_text_field($_REQUEST['menu-item-custom'][$menu_item_db_id]);
        update_post_meta($menu_item_db_id, '_menu_item_custom', $custom_value);
    }
}

3. Previewing Custom Fields in the Menu
To see your custom fields in the menu preview

 add_filter('wp_setup_nav_menu_item', 'setup_custom_nav_fields');
function setup_custom_nav_fields($menu_item) {
    $menu_item->custom = get_post_meta($menu_item->ID, '_menu_item_custom', true);
    return $menu_item;
}

4. Displaying Custom Fields in Frontend

 class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
    function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        $attributes = '';
        $description = '';
        
        !empty($item->custom) && $attributes .= ' data-custom="' . esc_attr($item->custom) . '"';
        
        $item_output = $args->before;
        $item_output .= '<a' . $attributes . ' href="' . esc_attr($item->url) . '">';
        $item_output .= $args->link_before . apply_filters('the_title', $item->title, $item->ID) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;
        
        $output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
    }
}

Then use your custom walker when calling wp_nav_menu():

 wp_nav_menu(array(
    'theme_location' => 'primary',
    'walker' => new Custom_Walker_Nav_Menu()
));

5. Adding JavaScript for Real-time Preview

 jQuery(document).ready(function($) {
    // Watch for changes in custom fields and update preview
    $(document).on('change', '.edit-menu-item-custom', function() {
        var input = $(this),
            menuItem = input.closest('.menu-item'),
            itemId = menuItem.find('.menu-item-data-db-id').val(),
            preview = $('#menu-item-preview-' + itemId);
            
        // Update preview element
        preview.find('.menu-item-title').attr('data-custom', input.val());
    });
});

Best Practices

  1. Always sanitize input when saving
  2. Use proper escaping when outputting values
  3. Prefix your custom field names to avoid conflicts
  4. Consider using WordPress’s built-in REST API for more complex previews
  5. Test with accessibility in mind

This approach gives you a solid foundation for extending WordPress menu items while maintaining compatibility with core functionality.

Related Posts


How to fork and maintain a WooCommerce Block separately?

Forking and maintaining a WooCommerce block allows you to customize its functionality independently ...

WordPress Page Loads Blank on Edit

When your WordPress page loads blank when you try to edit it, it often points to a conflict or resou...

WordPress Force HTTPS Without Plugin: The Ultimate Method

Hey there! Are you running a WordPress website on WP Engine and want to make it more secure? Worried...

“Maximum Available Quantity Has Been Added to Cart” Error in WooCommerce

Seeing the “Maximum available quantity has been added to cart” error in WooCommerce? Thi...

Recent Posts