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
- Always sanitize input when saving
- Use proper escaping when outputting values
- Prefix your custom field names to avoid conflicts
- Consider using WordPress’s built-in REST API for more complex previews
- Test with accessibility in mind
This approach gives you a solid foundation for extending WordPress menu items while maintaining compatibility with core functionality.