Adding Custom Attributes To WordPress Menus

They are so many tutorials on the web to explain “how to add a custom field to WordPress menus” but most of them treat about how to use the default CSS field of the default Description field. None of them gives a real method to add a real new field to menus. So, today we will create a simple plugin that will add a “subtitle” field to any WordPress menu.
Here is what the final result is in the WordPress administration:
Step 1: The Plugin Creation
Let’s go, as we saw in one of my previous post, creating a plugin isn’t complex. You just need to create a new folder under wp-content/plugins and called it “sweet-custom-menu”, and to create inside of it a file called “sweet-custom-menu.php”. Then open this file and add this code:
<?php
/*
Plugin Name: Sweet Custom Menu
Plugin URL: http://remicorson.com/sweet-custom-menu
Description: A little plugin to add attributes to WordPress menus
Version: 0.1
Author: Remi Corson
Author URI: http://remicorson.com
Contributors: corsonr
Text Domain: rc_scm
Domain Path: languages
*/
This code is all we need to define a new plugin. Now we are going to create a custom PHP class containing a constructor and the functions we will need to make the plugin work well.
Here is how to create the class:
class rc_sweet_custom_menu {
/*--------------------------------------------*
* Constructor
*--------------------------------------------*/
/**
* Initializes the plugin by setting localization, filters, and administration functions.
*/
function __construct() {
} // end constructor
/* All functions will be placed here */
}
// instantiate plugin's class
$GLOBALS['sweet_custom_menu'] = new rc_sweet_custom_menu();
The last line instantiates the class and add the whole class in a global variable.
Step 2: Adding Custom Fields Filter
Now that we have our class, we are going to create custom functions. The first function to add is the one that will register the “sub-title” custom field we want to be included as an advanced menu attribute. To do so, here the function to create, place this code instead of /* All functions will be placed here */ :
/**
* Add custom fields to $item nav object
* in order to be used in custom Walker
*
* @access public
* @since 1.0
* @return void
*/
function rc_scm_add_custom_nav_fields( $menu_item ) {
$menu_item->subtitle = get_post_meta( $menu_item->ID, '_menu_item_subtitle', true );
return $menu_item;
}
Then, we need to tell WordPress to take our function into account, so place the code below in the __construct() function:
// add custom menu fields to menu
add_filter( 'wp_setup_nav_menu_item', array( $this, 'rc_scm_add_custom_nav_fields' ) );
Step 3: Saving Custom Fields
Even if the custom “sub-title” field doesn”t really exist we must create a function that will save its value on menu edition. Menu elements are in two wards custom post types, so we can use the custom post type APIs and post_meta method. So, to save custom menu field value, add this function under rc_scm_add_custom_nav_fields():
/**
* Save menu custom fields
*
* @access public
* @since 1.0
* @return void
*/
function rc_scm_update_custom_nav_fields( $menu_id, $menu_item_db_id, $args ) {
// Check if element is properly sent
if ( is_array( $_REQUEST['menu-item-subtitle']) ) {
$subtitle_value = $_REQUEST['menu-item-subtitle'][$menu_item_db_id];
update_post_meta( $menu_item_db_id, '_menu_item_subtitle', $subtitle_value );
}
}
In this function, we are checking if the custom field value is sent from the form we ar about to create and then we simply store its value. Now we have to add the function to the appropriate hook. To do so, add this lines to __construct():
// save menu custom fields
add_action( 'wp_update_nav_menu_item', array( $this, 'rc_scm_update_custom_nav_fields'), 10, 3 );
Step 4: The Form
If you followed this tutorial step by step, you’ll probably guess that we haven’t created the form that must contain our sub-title field. This part is a bit more complex than the previous ones. This time we have to deal with Walker. I really encourage you to read the codex about the walker class, this will really help you to understand what it is and what it does. By the way they’re many great tutorials everywhere on the internet to give you more details about this class, so please check them out! In most part of the time, walkers are used to modify the HTML output of a menu. Here, we are working on the admin menu output form. Simply add this function to your main class:
/**
* Define new Walker edit
*
* @access public
* @since 1.0
* @return void
*/
function rc_scm_edit_walker($walker,$menu_id) {
return 'Walker_Nav_Menu_Edit_Custom';
}
and then this to the constructor:
// edit menu walker
add_filter( 'wp_edit_nav_menu_walker', array( $this, 'rc_scm_edit_walker'), 10, 2 );
What this does is replacing the default admin edit menu form by a custom one. Now that the filter, is added, copy these two lines at the bottom of the sweet-custom-menu.php file, outside the rc_sweet_custom_menu class:
include_once( 'edit_custom_walker.php' );
include_once( 'custom_walker.php' );
We are about to include two files. The first one “edit_custom_walker.php” is the one that will modify the default form to edit the menu. It’s in this file that we are going to add the sub-title field.
The second one is the welker used on the website frontend, this is the file that will modify the menu output to your visitors.
As the “edit_custom_walker.php” is a bit long, i am not going to paste the whole code. You can download the file here. The only code i added to it is from line 174 to line 185. Here is the added code:
<p class="field-custom description description-wide">
<label for="edit-menu-item-subtitle-<?php echo $item_id; ?>">
<?php _e( 'Subtitle' ); ?><br />
<input type="text" id="edit-menu-item-subtitle-<?php echo $item_id; ?>" class="widefat code edit-menu-item-custom" name="menu-item-subtitle[<?php echo $item_id; ?>]" value="<?php echo esc_attr( $item->subtitle ); ?>" />
</label>
</p>
If you want to add some more fields to the menu, simply duplicate these lines and copy & paste them. Once this step is done, the menu should be working in the administration. You should now be able to see the new “sub-title” field when adding a new element to a menu. If it’s not the case, make you followed the tutorial step by step. It’s now time time to output the subtitle value on the frontend.
Step 5: Custom Field Output
If everything is working fine in the administration, you probably now want to display the subtitle on the frontend. Open custom_walker.php and add thise code ot it:
<?php
/**
* Custom Walker
*
* @access public
* @since 1.0
* @return void
*/
class rc_scm_walker extends Walker_Nav_Menu
{
function start_el(&$output, $item, $depth, $args)
{
global $wp_query;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
$class_names = ' class="'. esc_attr( $class_names ) . '"';
$output .= $indent . '<li id="menu-item-'. $item->ID . '"' . $value . $class_names .'>';
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$prepend = '<strong>';
$append = '</strong>';
$description = ! empty( $item->description ) ? '<span>'.esc_attr( $item->description ).'</span>' : '';
if($depth != 0)
{
$description = $append = $prepend = "";
}
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before .$prepend.apply_filters( 'the_title', $item->title, $item->ID ).$append;
$item_output .= $description.$args->link_after;
$item_output .= ' '.$item->subtitle.'</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
This code is pretty standard. The only important thing here is these line:
$item_output .= ' '.$item->subtitle.'';
Step 6: Call The Walker!
In the last step you will need to open the header.php file in your theme folder. It’s where most part of the time the menu is called. If not, then contact the theme creator to know where it’s called.
Find the wp_nav_menu() function.
Then simply add the walker parameter:
'walker' => 'rc_scm_walker'
That’s it ! I hope you enjoyed adding a custom field to a WordPress menu. You can now modify a bit this code to add other fields such as checkboxes, select dropdown, textareas etc… Feel free to add a comment in the comments section. If you found a bug, if you improve it etc…
Sweet Custom Menu Plugin
If you are looking to implement this tutorial you can download the “Sweet Custom Menu Plugin” I’ve created to get you started. Enjoy!


Remi, This is awesome! I had no idea you could do this. Thanks for the great tutorial.
Thanks Gregg, this tutorial is just a simple example, but you could do much more using this method. I’m currently working on an awesome plugin related to WordPress menus that should be really handful!
Can’t wait to see what you’re working on!
All i can say is that i am doing it in parntership with the codecanyon top seller!!!
Sounds good but missing a showcase. And got a question: Could I add in this way some more targets (_top) in menu link?
Hi Mark, i sent the plugin to the WordPress official repo, so you can downolad and use it easily. And to answer your question, yes you can use this method to add links target (even id wordpress gives you by default a checkbox to open a link in a new window)
I tried this using a twenty-twelve child theme and get this error when I add the walker parameter to headers.php:
Fatal error: Using $this when not in object context in …/wp-includes/class-wp-walker.php on line 185
I’m pretty sure I followed the instructions that came with the plugin correctly.
Hi,Remi
I got these notice after using debug bar plugin : Trying to get property of non-object online : 39 41 42 44 please help please.
Best regards,
Decneo
Where did you place this code ?
Hi, Remi
I’ve placed this code on my theme_functions.php
Best regards,
Decneo
This might be asking a lot, but this is the closest I’ve come across in my research so far.
I’m looking to add a data-reveal-id=”" field to my menu items (specifically one of them) to trigger a PopPop! (http://wordpress.org/extend/plugins/poppop/) modal window.
I added the additional section to the edit_custom_walker.php file like you recommended:
<label for="edit-menu-item-subtitle-”>
<input type="text" id="edit-menu-item-subtitle-” class=”widefat code edit-menu-item-custom” name=”menu-item-subtitle[]” value=”data-reveal-id ); ?>” />
And the attribute item appears in my Menu screen now, but I’m not sure how to modify this part to ensure that it actually outputs the field’s contents into the front-end of my site:
$item_output .= ‘ ‘.$item->subtitle.”;
Also, currently, whatever I type in the field reverts to a value of 0 when I save and replaces the subtitle value. Could be related to the above unmodified section, but I thought I’d post that too, in case it was indicative of me missing something else along the way.
Thanks in advance if you get the opportunity to answer this.
- Steven
Never mind, I just used a ‘JQuery var newHTML =’ function to find and replace the menu item in question.
Sorry no one ever got back to you for the initial comment! Glad you got it working though ;)
The plugin is not working. Like already said above it gives an error: Fatal error: Using $this when not in object context in /wp-includes/class-wp-walker.php on line 185.
Do you have a working version?
Just to let other know, the description at http://wordpress.org/extend/plugins/sweet-custom-menu/installation/ is wrong, you should define the walker in wp_nav_menu like this: ‘walker’ => new rc_scm_walker
Hi Alex, thanks for mentioning the error. You’re totally true. I have to update the description.
awesome! now I can have everything I want on my menu
I´m writting ‘walker’ => ‘rc_scm_walker’ and I get the error: Using $this when not in object context in /wp-includes/class-wp-walker.php on line 185.
What I need to write or to change to make functional this plugin?