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 - http://cl.ly/0v2Z0X1n2e1L431t0h1G)

  • As far as I know, it's totally not possible, however would be great if you elaborate your escenario so we can know exactly what are you trying to achieve ;) – andresmijares25 Nov 13 '11 at 4:16
  • My use case is something close to the 'class' option, but I need to provide a <select> item, instead of having users enter the class names. – Dogbert Nov 13 '11 at 7:46
  • You could add it per Javascript. There seems to be no other way. – fuxia Nov 13 '11 at 12:06
  • I'm gonna CRY!!!!11one – frnhr Jan 22 '13 at 0:13
up vote 6 down vote accepted

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();


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 (
                                !$(e.target).hasClass("icon-link-was-added") &&
                                $(e.target).is("li") &&
                                $(e.target).attr("id").search(/menu\-item\-[0-9]+/) !== -1) {

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

add_action('admin_footer', 'menu_item_class_select');
  • Please add the code to your answer - thanks. – Nicolai Sep 30 '15 at 13:09

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.