Menu items are saved as posts with the post_type “nav_menu_item”. This means you can assign postmeta to them just like you can with posts and create custom fields in the menu edit screen to enter an img url or assign a media ID.
You can follow this tutorial about adding custom meta to menu items to suit your needs. If you need help, I can throw together some code bits to get you rolling.
UPDATE
I put together this bit that allows you to drop into functions.php. Untested, but should work. It’s a different approach then the link I originally shared but it’s a method I’ve used in the past.
//Adds custom data field form
function wpse_259995_menu_custom_fields_fields( $id, $item, $depth, $args ) {
$_key = 'img_url';
$label = "Image URL";
$key = sprintf( 'menu-item-%s', $_key );
$id = sprintf( 'edit-%s-%s', $key, $item->ID );
$name = sprintf( '%s[%s]', $key, $item->ID );
$value = get_post_meta( $item->ID, $key, true );
$class = sprintf( 'field-%s', $_key );
printf(
'<p class="%5$s"><label for="%1$s">%2$s<br />
<input type="text" id="%1$s" class="%5$s" name="%3$s" value="%4$s" /></label></p>',
esc_attr( $id ),
esc_html( $label ),
esc_attr( $name ),
esc_url( $value ),
esc_attr( $class )
);
}
add_action( 'wp_nav_menu_item_custom_fields', 'wpse_259995_menu_custom_fields_fields', 10, 4 );
//Save Custom Menu data
function wpse_259995_menu_custom_fields_save( $menu_id, $menu_item_db_id, $menu_item_args ) {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' );
$_key = 'img_url';
$label = "Image URL";
$key = sprintf( 'menu-item-%s', $_key );
// Sanitize
if ( ! empty( $_POST[ $key ][ $menu_item_db_id ] ) ) {
// Do some checks here...
$value = $_POST[ $key ][ $menu_item_db_id ];
}
else {
$value = null;
}
// Update
if ( ! is_null( $value ) ) {
update_post_meta( $menu_item_db_id, $key, $value );
}
else {
delete_post_meta( $menu_item_db_id, $key );
}
}
add_action( 'wp_update_nav_menu_item', 'wpse_259995_menu_custom_fields_save', 10, 3 );
//Adds toggle to show field in screen options
function wpse_259995_menu_custom_fields_columns( $columns ) {
$fields = array('img_url' => "Imae URL");
$columns = array_merge( $columns, $fields );
return $columns;
}
add_filter( 'manage_nav-menus_columns', 'wpse_259995_menu_custom_fields_columns', 99 );
//Modify the element of the WP walker
function wpse_259995_walker_nav_menu_start_el_menu_with_images ($item_output, $item, $depth, $args ){
$attr_title = ! empty( $item->attr_title ) ? $item->attr_title : $item->title;
$img_url = get_post_meta( $item->ID, 'menu-item-img_url', true );
$item_output = $args->before;
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . '<img src="'.$img_url.'" />' . $args->link_after;
$item_output .= $args->after;
return $item_output;
}
Then, in your header.php (or wherever you are calling your menu):
add_filter( 'walker_nav_menu_start_el', 'wpse_259995_walker_nav_menu_start_el_menu_with_images', 10, 4 );
wp_nav_menu( /*Call your menu*/ );
remove_filter( 'walker_nav_menu_start_el', 'wpse_259995_walker_nav_menu_start_el_menu_with_images', 10, 4 );
UPDATE
I forgot that when I used this solution a few years ago it was in conjunction with this plugin that provided the hook “wp_nav_menu_item_custom_fields”
https://wordpress.org/plugins/menu-item-custom-fields/installation/
You’ll need to install that plugin (or reuse the code) to get this to work.