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


WordPress $wpdb->update() Not Updating postmeta? Here’s Why and How to Fix It

Here’s an explanation and solution for why $wpdb->update() might not be working for your Wo...

Fail2Ban plugin on DigitalOcean

Fail2Ban is an excellent security tool for any Linux server, and DigitalOcean Droplets are no except...

WordPress Redirect Issues Across Multiple Websites

Experiencing redirect loops or unwanted redirects on multiple WordPress sites? Here’s a guide to d...

WordPress Subdirectory Setup for Root Domain

WordPress Subdirectory Setup for Root Domain This guide assumes your WordPress installation is in a ...

Recent Posts