Add custom menu item metabox in Navigation Settings

Super old question I know, but it came up when I was posting a similar question about doing the same thing in the Customizer.

Prior to WordPress 5.4, the only way to add field to the menu items was by replacing the Walker_Nav_Menu_Edit walker with something custom. This had the major downside in that you can only ever have 1 walker and caused a lot of conflicts.

After 8 long years, WP 5.4 introduced the wp_nav_menu_item_custom_fields hook and here is some example code on how to use that hook.

First, we’ll display a text input:

/**
* Add custom fields to menu item
*
* This will allow us to play nicely with any other plugin that is adding the same hook
*
* @param  int $item_id 
* @params obj $item - the menu item
* @params array $args
*/
function kia_custom_fields( $item_id, $item ) {

    wp_nonce_field( 'custom_menu_meta_nonce', '_custom_menu_meta_nonce_name' );
    $custom_menu_meta = get_post_meta( $item_id, '_custom_menu_meta', true );
    ?>
    <div class="field-custom_menu_meta description-wide" style="margin: 5px 0;">
        <span class="description"><?php _e( "Extra Field", 'custom-menu-meta' ); ?></span>
        <br />

        <input type="hidden" class="nav-menu-id" value="<?php echo $item_id ;?>" />

        <div class="logged-input-holder">
            <input type="text" name="custom_menu_meta[<?php echo $item_id ;?>]" id="custom-menu-meta-for-<?php echo $item_id ;?>" value="<?php echo esc_attr( $custom_menu_meta ); ?>" />
            <label for="custom-menu-meta-for-<?php echo $item_id ;?>">
                <?php _e( 'Custom menu text', 'custom-menu-meta'); ?>
            </label>
        </div>

    </div>

    <?php
}
add_action( 'wp_nav_menu_item_custom_fields', 'kia_custom_fields', 10, 2 );

Then we will save the data. Since menu items are a WordPress custom post type, dealing with the data is same as adding and retrieving post_meta a regular post or page.

/**
* Save the menu item meta
* 
* @param int $menu_id
* @param int $menu_item_db_id   
*/
function kia_nav_update( $menu_id, $menu_item_db_id ) {

    // Verify this came from our screen and with proper authorization.
    if ( ! isset( $_POST['_custom_menu_meta_nonce_name'] ) || ! wp_verify_nonce( $_POST['_custom_menu_meta_nonce_name'], 'custom_menu_meta_nonce' ) ) {
        return $menu_id;
    }

    if ( isset( $_POST['custom_menu_meta'][$menu_item_db_id]  ) ) {
        $sanitized_data = sanitize_text_field( $_POST['custom_menu_meta'][$menu_item_db_id] );
        update_post_meta( $menu_item_db_id, '_custom_menu_meta', $sanitized_data );
    } else {
        delete_post_meta( $menu_item_db_id, '_custom_menu_meta' );
    }
}
add_action( 'wp_update_nav_menu_item', 'kia_nav_update', 10, 2 );

Finally, we can displaying some extra text after the menu title.

/**
* Displays text on the front-end.
*
* @param string   $title The menu item's title.
* @param WP_Post  $item  The current menu item.
* @return string      
*/
function kia_custom_menu_title( $title, $item ) {

    if( is_object( $item ) && isset( $item->ID ) ) {

        $custom_menu_meta = get_post_meta( $item->ID, '_custom_menu_meta', true );

        if ( ! empty( $custom_menu_meta ) ) {
            $title .= ' - ' . $custom_menu_meta;
        }
    }
    return $title;
}
add_filter( 'nav_menu_item_title', 'kia_custom_menu_title', 10, 2 );

My full blog post on adding custom fields to WordPress menu items and the full source code