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 detect YouTube iframe tag in a specific WordPress post

Detecting YouTube Iframes in WordPress Posts This document outlines two primary methods for detectin...

How to Retrieve and Expose Gutenberg & Theme-Generated CSS in WordPress

If you want to extract the CSS generated by Gutenberg and your WordPress theme, you can do so using ...

WP Pagination Not Working for Custom Query: Troubleshooting Tips

Ever struggled with WordPress pagination code? Fret not! We’ve got your back. In this post, we...

Why Your Post Counts Are Wrong on Multilingual Sites (And How to Fix It)

Managing a multilingual website can be a complex endeavor, and one common issue that often goes unno...

Recent Posts