Add custom meta to nav menu items

The question:

I need to attach meta data to every menu item, with a key ‘foo’. Is it possible to do that, without editing core WP?

A quick look at the nav-menu files showed that no hooks exist near the place I want to add the input box (below Description, here –

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

here is a quick code that should do the job, paste this in your theme’s functions.php file

basically what it does is hide all regular class input boxes and adds a new select dropdown which changes the value of the hidden input based on the selected value.

and it looks like this;

enter image description here

function menu_item_class_select(){
    global $pagenow;
    if ($pagenow == "nav-menus.php"){
        function create_dd(v){
            //create dropdown
            var dd = jQuery('<select class="my_class"></select>');
            //create dropdown options
            //array with the options you want
            var classes = ["","class1","class2","class3"];
            jQuery.each(classes, function(i,val) {
                if (v == val){
                    dd.append('<option value="'+val+'" selected="selected">'+val+'</option>');
                    dd.append('<option value="'+val+'">'+val+'</option>');
            return dd;

        jQuery(".edit-menu-item-classes").each(function() {
            //add dropdown
            var t = create_dd(jQuery(this).val());
            //hide all inputs

        //update input on selection
        jQuery(".my_class").bind("change", function() {
            var v = jQuery(this).val();
            var inp = jQuery(this).next();


Method 2

this answer in here is little bit of overdoing. I prefer to do not interfere with walkers in this case so I did my job with more jquery involved.

add_action('wp_update_nav_menu_item', 'custom_nav_update', 10, 3);
function custom_nav_update($menu_id, $menu_item_db_id, $args)
    if (is_array($_REQUEST['menu-item-icon'])) {
        $custom_value = $_REQUEST['menu-item-icon'][$menu_item_db_id];
        update_post_meta($menu_item_db_id, '_menu_item_icon', $custom_value);

function menu_item_class_select()
    global $pagenow;
    if ($pagenow == "nav-menus.php") {
            (function ($) {
                $(document).ready(function () {
                    var menu_item_collection = {};
                    var item_holder = $("#menu-to-edit");
                    menu_item_collection.items = item_holder.find("li");

                    // extract id of a menu item from this pattern (menu-item-109)
                    // which 109 is the id
                    function getId(item_id) {
                        var arrayed = item_id.split("-");
                        return arrayed[2];

                    // return template wrapped in jquery object
                    function extra_field(id, value) {
                        if (value === null) {
                            value = "";
                        var template = '<p class="field-title-attribute description description-wide">' +
                            '<label for="edit-menu-item-attr-title-108">' +
                            'icon' +
                            '<input type="text" class="widefat edit-menu-item-attr-title" name="menu-item-icon[' + id + ']" value="' + value + '">' +
                            '</label>' +

                        return $(template);

                    // ajax out to get metadata
                    function getMetaData(id, callback) {
                            method: "POST",
                            url: "/wp-admin/admin-ajax.php",
                            data: {id: id, post_type: "menu", action: "menu_metadata"}
                        }).done(function (msg) {
                        }).fail(function (msg) {
                            console.log("failed : " + msg);

                    // these lines of codes initialize menus with their custom attributes values
                    if (menu_item_collection.items.length > 0) {
                        var id;
                        menu_item_collection.items.each(function () {
                            id = getId($(this).attr("id"));
                            var _this = $(this);
                            getMetaData(id, function (msg) {
                                var attribute = (_this.find(".field-title-attribute"));
                                if (msg._menu_item_icon === 'undefined') {
                                    msg._menu_item_icon = "";
                                attribute.after(extra_field(getId(_this.attr('id')), msg._menu_item_icon[0]));

                    // this listener interact with adding live menus
                    item_holder.on('DOMNodeInserted', function (e) {
                        try {
                            // sortable script code that they used made me to check upon whether our intended child is li or not
                            // yet i wrap this code into try catch because some nodes was`nt inserted to the dome
                            // and null pointer would cause undefined error some times
                            if (
                                !$("icon-link-was-added") &&
                                $("li") &&
                                $("id").search(/menu-item-[0-9]+/) !== -1) {

                                var attribute = ($(".field-title-attribute"));
                        } catch (e) {
                            // silent

add_action('admin_footer', 'menu_item_class_select');

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment