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


Why Your JavaScript for Quick WordPress Data Saving Isn’t Working

Many WordPress developers try to implement quick-saving functionality for custom fields to improve w...

How to Prevent Category Pages from Highlighting the Blog Menu in WordPress

When category archive pages automatically highlight your blog menu item in WordPress, it’s bec...

How to Fix CSP Inline Script & Style Issues in WordPress

Content Security Policy (CSP) is a crucial security layer that helps protect your WordPress site fro...

Why WordPress Looks for .htaccess in the Wrong Directory When Using WP_HOME and WP_SITEURL

When you configure WP_HOME and WP_SITEURL in your WordPress wp-config.php file, especially when they...

Recent Posts