WordPress navigation menus support custom fields per menu item — data stored as post meta on the underlying nav_menu_item post type. These fields let you attach an icon class, a “New!” badge label, a custom CSS class beyond what WordPress provides in the “CSS Classes” field, an ARIA description, or a mega-menu column count to each individual link in the menu. WordPress 5.4 added the wp_nav_menu_item_custom_fields action hook, which fires inside the menu item edit form in the admin, giving plugins and themes a supported way to add input fields without hacking the Walker. Saving uses the wp_update_nav_menu_item hook which fires after each menu item is saved. Reading the stored value in a template uses get_post_meta() on the menu item’s post ID. For outputting the value in the rendered HTML, you can use a nav_menu_link_attributes filter for simple attribute injection, or a custom Walker for more complex markup changes.
Problem: You want editors to assign a Font Awesome icon class to each navigation menu item from the menu editor, so the rendered menu outputs <i class="fa fa-home"></i> before each link label.
Solution: Add an icon field to the menu item form via wp_nav_menu_item_custom_fields, save it with update_post_meta() on wp_update_nav_menu_item, and inject the icon HTML via the walker_nav_menu_start_el filter.
<?php
// ── 1. Add field to the menu item editor ──────────────────────────────
add_action( 'wp_nav_menu_item_custom_fields', 'add_menu_item_icon_field', 10, 4 );
function add_menu_item_icon_field( $item_id, $item, $depth, $args ) {
$icon = esc_attr( get_post_meta( $item_id, '_menu_item_icon', true ) );
?>
<p class="field-icon description description-wide">
<label for="edit-menu-item-icon-<?php echo esc_attr( $item_id ); ?>">
<?php esc_html_e( 'Icon Class (e.g. fa fa-home)', 'textdomain' ); ?><br>
<input type="text"
id="edit-menu-item-icon-<?php echo esc_attr( $item_id ); ?>"
class="widefat"
name="menu-item-icon[<?php echo esc_attr( $item_id ); ?>]"
value="<?php echo $icon; ?>">
</label>
</p>
<?php
}
// ── 2. Save the field when the menu is saved ───────────────────────────
add_action( 'wp_update_nav_menu_item', 'save_menu_item_icon_field', 10, 3 );
function save_menu_item_icon_field( $menu_id, $menu_item_db_id, $menu_item_data ) {
if ( isset( $_POST['menu-item-icon'][ $menu_item_db_id ] ) ) {
$icon = sanitize_html_class( $_POST['menu-item-icon'][ $menu_item_db_id ] );
update_post_meta( $menu_item_db_id, '_menu_item_icon', $icon );
} else {
delete_post_meta( $menu_item_db_id, '_menu_item_icon' );
}
}
// ── 3. Inject the icon into the rendered menu link ────────────────────
add_filter( 'walker_nav_menu_start_el', 'prepend_icon_to_menu_item', 10, 4 );
function prepend_icon_to_menu_item( $item_output, $item, $depth, $args ) {
$icon = get_post_meta( $item->ID, '_menu_item_icon', true );
if ( $icon ) {
$icon_html = '<i class="' . esc_attr( $icon ) . '" aria-hidden="true"></i>';
$item_output = str_replace( $args->link_before, $args->link_before . $icon_html, $item_output );
}
return $item_output;
}
NOTE: The wp_nav_menu_item_custom_fields action was added in WordPress 5.4. For older WordPress versions the same effect requires overriding the Walker_Nav_Menu_Edit class — significantly more code. If you need to support WP < 5.4, use the wp_edit_nav_menu_walker filter to substitute a custom walker class that extends Walker_Nav_Menu_Edit and overrides the start_el method.