Software, Development, and WordPress

Add a Separator To The WordPress Menu

As far as the WordPress community is concerned, developers tend to be divided on how plugins and themes should insert themselves into the WordPress menu.

On one hand, some feel that because WordPress offers the ability to insert custom menu items into existing menus, then plugin and theme content should follow suit and exist only in the predefined menu; however, others feel that because WordPress offers the ability to define custom menu items, then they should take advantage of it.

In fact, opinionated plugins have been written specifically for on this.

I don’t have a hard stance on this issue – though I tend to lean in the direction of adding new functionality to existing menus, I think that there are perfectly suitable cases for defining custom menus.

But if you’re going to define a custom menu item, then you should place it logically among the existing menu items: If you’re writing a theme, its menu should be placed near the ‘Appearance’ options rather than, say, the ‘Dashboard’ options. Then again, there are times where makes sense to isolate a menu item to its own section. In order to do this, a custom separator may need to be added in addition to the menu.

Adding a separator to the WordPress menu isn’t difficult and though there are a variety of ways to do it, the easiest way to do about doing so is using a couple of custom functions registered with the WordPress API.

Below, I give a working example for how to add a separator to the WordPress menu. I also look at how the default menu is structured, the proper way to add your own separator, and an example plugin for doing just that. If you’re an advanced developer, it may be more beneficial (and quicker!) for you to refer to the source code on GitHub.

Understanding The Default Menu

Before adding a separator to the WordPress menu, it’s important to understand how the default menu is structured. So, assuming that you’re looking at a default installation, the default menu is structured like this:

    [2] => Array
            [0] => Dashboard
            [1] => read
            [2] => index.php
            [3] =>
            [4] => menu-top menu-top-first menu-icon-dashboard menu-top-last
            [5] => menu-dashboard
            [6] => div

    [4] => Array
            [0] =>
            [1] => read
            [2] => separator1
            [3] =>
            [4] => wp-menu-separator

    [5] => Array
            [0] => Posts
            [1] => edit_posts
            [2] => edit.php
            [3] =>
            [4] => open-if-no-js menu-top menu-icon-post menu-top-first
            [5] => menu-posts
            [6] => div

    [10] => Array
            [0] => Media
            [1] => upload_files
            [2] => upload.php
            [3] =>
            [4] => menu-top menu-icon-media
            [5] => menu-media
            [6] => div

    [15] => Array
            [0] => Links
            [1] => manage_links
            [2] => link-manager.php
            [3] =>
            [4] => menu-top menu-icon-links
            [5] => menu-links
            [6] => div

    [20] => Array
            [0] => Pages
            [1] => edit_pages
            [2] => edit.php?post_type=page
            [3] =>
            [4] => menu-top menu-icon-page
            [5] => menu-pages
            [6] => div

    [25] => Array
            [0] => Comments <span class="awaiting-mod count-0"><span class="pending-count">0</span></span>
            [1] => edit_posts
            [2] => edit-comments.php
            [3] =>
            [4] => menu-top menu-icon-comments menu-top-last menu-top-last
            [5] => menu-comments
            [6] => div

    [59] => Array
            [0] =>
            [1] => read
            [2] => separator2
            [3] =>
            [4] => wp-menu-separator

    [60] => Array
            [0] => Appearance
            [1] => switch_themes
            [2] => themes.php
            [3] =>
            [4] => menu-top menu-icon-appearance menu-top-first
            [5] => menu-appearance
            [6] => div

    [65] => Array
            [0] => Plugins <span class="update-plugins count-0"><span class="plugin-count">0</span></span>
            [1] => activate_plugins
            [2] => plugins.php
            [3] =>
            [4] => menu-top menu-icon-plugins
            [5] => menu-plugins
            [6] => div

    [70] => Array
            [0] => Users
            [1] => list_users
            [2] => users.php
            [3] =>
            [4] => menu-top menu-icon-users
            [5] => menu-users
            [6] => div

    [75] => Array
            [0] => Tools
            [1] => edit_posts
            [2] => tools.php
            [3] =>
            [4] => menu-top menu-icon-tools
            [5] => menu-tools
            [6] => div

    [80] => Array
            [0] => Settings
            [1] => manage_options
            [2] => options-general.php
            [3] =>
            [4] => menu-top menu-icon-settings menu-top-last
            [5] => menu-settings
            [6] => div


Simply put, the menu is an array of arrays. Looking at the code, it’s easy to see how the menu is organized. The key takeaway from this is that there are certain positions that are reserved for default menu items.

Specifically, the following positions are reserved:

  • 2. Dashboard
  • 4. Separator
  • 5. Posts
  • 10. Media
  • 15. Links
  • 20. Pages
  • 59. Separator
  • 60. Appearance
  • 65. Plugins
  • 70. Users
  • 75. Tools
  • 80. Settings

When adding a separator to the WordPress menu, avoid using any of the positions listed above.

Add a Separator To The WordPress Menu

A couple of functions will need to be written in order to add a custom separator. These can either be written within a theme’s functions.php file or a plugin file.

Registering a Custom Action

For adding a custom separator to the WordPress menu, a custom function will need to be registered with the WordPress API. This can be done using add_action. Below, I’m defining a custom action called ‘init_custom_menu_separator’ and a custom function named ‘add_admin_menu_separator.’

add_action( 'admin_init', 'add_admin_menu_separator' );

The function itself looks like this:

function add_admin_menu_separator( $position ) {

	global $menu;

	$menu[ $position ] = array(
		0	=>	'',
		1	=>	'read',
		2	=>	'separator' . $position,
		3	=>	'',
		4	=>	'wp-menu-separator'


Some points about the code:

  • The function accepts a single parameter – the position – this value represents where to insert the separator
  • The WordPress menu is defined at the global level so, in order to manipulate it, we must scope it within our function
  • We create our own array representing a separator. This is based on how existing menu separators are defined in the WordPress menu
  • We insert it into the WordPress menu using the position reference passed into the function

For those that are curious, you can learn more about what each index of the array represents by taking a look at the source code of the plugin on GitHub.

Running The Custom Action

At this point, the custom hook and functionality are defined for adding a custom separator to the WordPress menu, but there’s nothing invoking the actual function. To do this, one more action has to be defined.

So, I’ll register a new function using add_action and the WordPress admin_menu hook, thanks to a suggestion from Jamie.

My function is called ‘set_admin_separator.’ The add_action call is simple:

add_action( 'admin_menu', 'set_admin_menu_separator' );

At this point, all that’s left is to define the actual function. It’s a single line:

function set_admin_menu_separator() {
	do_action( 'admin_init', 79 );
} // end set_admin_menu_separator

This function uses WordPress’ do_action method for invoking the custom action that I defined earlier in the code. The second argument – 79 – is the value that’s passed into the function.

Basically, this function is telling WordPress to call the function that I’ve registered with the ‘admin_init’ hook (which is the ‘add_menu_separator’ function) and pass it the value of 79 (which will, in turn, be used as the position).

Once executed, this will add a custom menu separator above WordPress’ ‘Settings’ menu.

WordPress Custom Menu Separator Plugin

I’ve written a plugin that demonstrates all of the above code. You can grab it on GitHub. Once it’s installed, you can activate it in your Plugin’s dashboard page.

Just after activation (and assuming it’s a vanilla install of WordPress or no other plugins are interfering with the process) you should see a new separator appear above the ‘Settings’ menu.


  1. Pippinsplugins (@pippinsplugins)

    Nice dude.

  2. Jamie

    Thanks Tom, a well written plugin.

    So this isn’t called on every page using the init hook, you can use:
    add_action( ‘admin_menu’, ‘set_admin_menu_separator’ );

  3. Jamie

    I’ve shortened it even further and added this to functions.php:

    add_action( 'admin_menu', 'set_admin_menu_separator' );
    function set_admin_menu_separator() {
    $position = 79;
    global $menu;
    $menu[$position] = array(
    0 => '',
    1 => 'read',
    2 => 'separator' . $position,
    3 => '',
    4 => 'wp-menu-separator'

  4. Amber

    Neat! Thanks Tom.

    On a side note, this doesn’t work for me in a fresh install of WP 3.5.1 using add_action( ‘admin_menu’, ‘set_admin_menu_separator’ ); (as per Jamie’s suggestion and your updated article).

    It only works when using add_action( ‘init’, ‘set_admin_menu_separator’ );

    • Tom McFarlin

      Thanks Amber – I’ll into this as soon as I get a chance and will update the post accordingly (giving you props, where props are due :).

    • Jamie

      Does this work?
      add_action('admin_menu', 'wpautop_control_menu');
      function wpautop_control_menu() {
      add_submenu_page('options-general.php', 'wpautop-control', 'wpautop control', 'manage_options', 'wpautop-control-menu', 'wpautop_control_options');


    • Tom McFarlin

      I just updated the GitHub Repository with an updated plugin and will update the code in the post.

      Thanks for bringing this to my attention.

      May your menus be ever separated :).

  5. Amber

    Wow, you both responded super fast. :)

    @Jamie: If I pop that code into my functions.php yes it does work. It creates a sub-menu in my Settings for “wpautop control”

    • Jamie

      What code are you using in your function in that case?

  6. Amber

    Hi Jamie,

    I totally derped and literally just copied/pasted the code showed above (without making modifications to try to get it to work with a separator).

    For the life of me all the ways that I tried the original code coupled with admin_menu or using add_menu_page would not display that darned separator. The only way I got it to work was using init.

    I stumbled on this link doing a while doing a search for ways to get that sucker to display:

    It appears to work using admin_menu and add_menu_page/add_submenu_page similar to the last sample you posted.

    I also found this link:

    The above has a smaller amount of code but the only problem there is it won’t put a separator at say 81 (after Settings).

  7. Rena

    I wish WordPress provide real API functions for to create menu. How do i know that other plugin won’t replace my menu?

    • Tom McFarlin

      They do provide an API for that.

      Check out:



      Also, if you’re worried about another plugin replacing your menu, you may be approaching it incorrectly – plugins shouldn’t do that to other plugins. Using the proper API functions prevent this from happening.

  8. Amber

    Hi Tom,

    Just thought I’d post that I tried to use your plugin from GitHub on a site (as a test) and it doesn’t appear to be working. It seems to clash with plugins such as WooCommerce (and a few others) and generates a “Fatal error: Call to undefined function add_settings_section()”.

    However, when using your instructions in the functions.php this doesn’t occur.

    One other really weird thing I noticed is that when the plugin “Plugin Notes” is used, and the instructions for the separator are added via functions.php all the plugins now have a duplicate “add plugin note” link and plugins that have a note have duplicate links. This resolves itself when the separator code is removed from functions.php. Mind you… the Plugin Notes plugin is super old (while still functional) but I just thought I would mention it in case someone else uses your code (awesome) with that plugin and runs into the same problem.


    • Tom McFarlin

      Hey Amber – which plugin are you referring to? The separate code mentioned in this post doesn’t make a call to add_settings_section.

      A couple of other tutorial articles do, but they are theme based and they should work as it’s part of the WordPress API.

      Let me know – I’m more than happy to try to resolve this (and thanks for bringing it to my attention :)).

      • Amber

        Hi Tom,

        So far I have found that if I try to activate the Github plugin, it causes a fatal error when the following plugins are active: WooCommerce, Code Snippets, and WP-Invoice.

        So for example if I have a site that has WooCommerce activated, and I download/install/activate the plugin from github the error is:

        Fatal error: Call to undefined function add_settings_section() in /wp-content/plugins/woocommerce/admin/woocommerce-admin-init.php on line 806

        Line 806 in that file is as follows:
        add_settings_section( ‘woocommerce-permalink’, __( ‘Product permalink base’, ‘woocommerce’ ), ‘woocommerce_permalink_settings’, ‘permalink’ );

        When it’s used with the Code Snippets plugin if I attempt to activate the github separator plugin it generates:

        Fatal error: Call to undefined function add_settings_section() in /wp-content/plugins/code-snippet-library/classes/class-code-snippet-library-settings.php on line 36

        Line 36 in that file is as follows: add_settings_section( ‘snippet_theme_settings’ , __( ‘Modify your code snippet theme’ , ‘code_snippet’ ) , array( &$this , ‘main_settings’ ) , ‘code_snippet_settings’ );

        When none of the above plugins are active then I can successfully activate the Separator plugin without errors.

        If I use the code in this article directly in my theme’s function.php file instead of the github separator plugin (while all of the above plugins are active) there are no error’s at all.

        • Amber

          Hi Tom,

          This just gets weirder and weirder for me… so down the rabbit hole we go!

          I think in the github plugin the line that’s causing the fatal error issues is:

          add_action( ‘init’, array( $this, ‘set_admin_menu_separator’ ) );

          If I change that line to:

          add_action( ‘admin_menu’, array( $this, ‘set_admin_menu_separator’ ) );

          It activates without any fatal errors, but doesn’t appear to work properly on default installs. Making the above change adds the separator underneath “Settings” (even though it could be set for 79 or 6, etc.)

          One thing that I have noticed while trying this script on both fresh WP installs (no plugins and using Twenty Twelve) and a few other installs (have multiple plugins activated) is that on default sites the code to put into the fuctions.php in this article doesn’t quite work.

          It will create the separator but not at position 79, it pushes it under “Settings” and if you change it to say position 6; it doesn’t move (same as the plugin after modifying init to admin_menu).

          The only way that I got it to work (by default) was to change the code to:

          add_action( ‘init’, ‘set_admin_menu_separator’ );

          function set_admin_menu_separator() {
          $position = 79;
          global $menu;
          $menu[$position] = array(
          0 => ”,
          1 => ‘read’,
          2 => ‘separator’ . $position,
          3 => ”,
          4 => ‘wp-menu-separator’

          In the above using admin_menu or admin_init in replace of init did not work. Using admin_menu results in no separator showing and admin_init loads the separator under “Settings”.

          Here’s where it gets odd, or so I think.

          The only time that the code that you listed in your article works and adds the separator in the correct position is when WooCommerce is activated.

          If WooCommerce is active it will place the Separator using the code in this tutorial at wherever it’s been defined with 0 issues. If I activate WooCommerce and the Code Snippets (mentioned in my previous post), the separator continues to function.

          If I deactivate WooCommerce and keep only Code Snippets active, the separator only shows in position 6 (which is the position being used by Code Snippets); any position past 6 and it goes back to loading under “Settings”.

          The only way I have been able to get the code snippet in your tutorial to function in all positions I have defined is if WooCommerce has been activated first (I’m pretty sure I saw something in their code once about it modifying the menu but I could be wrong).

          If I again deactivate WooCommerce (no plugins) I’m back to the separator not loading in any point other than after “Settings” unless I use the code posted earlier in this comment.

          Sorry for writing such a book about this!

  9. Amber

    Additionally… what would be the best way to use this if you wanted to create multiple separators?

    • Tom McFarlin

      Hey Amber,

      A lot of these questions are going beyond support for some sample code ;).

      Ultimately, the purpose of the post was to show how you could introduce a separator into the WordPress admin. If the code above is causing conflicts with an existing plugin, I’m all for changing up which hook it uses (and I like having it documented here if it works for you), but I can’t really offer support via comments for this in one off cases.

      That said, the best way to create multiple separators would be this:

      Create a function that accepts a number as an argument
      Call the function however many times you need passing a number into it
      Generalize the code in the function to using the incoming argument as where to place the separator

      If the function is made public, you can do so from outside of the class itself; otherwise, you could call it whenever the plugin is instantiated (so call it within the constructor).

      Perhaps something like this:

      $this->add_separator( 2 )

      You could even generalize the function to accept an array of arguments then write a loop that iterators through each of the arguments adding the separator.

      So then that’d look like this:

      $this->add_separator( array( 2, 4, 6, 8 ) );

      And the core function would be:

      function add_separator( $arr_locations ) {
      foreach( $arr_locations as $location ) {
      // Add your separator here

      There’s a variety of ways to do it :).

  10. Amber

    Hi Tom,

    You put me on the right track. I was able to achieve this via the following modified code (not sure if it’s the best way but it works):

    /* Rocket Boosters Engaged! Set Coordinates */
    function set_admin_menu_separator( ) {
    $positions = array ( 'sep1'=>200, 'sep2'=>201, 'sep3'=>202, 'sep4'=>203 );
    foreach($positions as $position) {
    do_action( 'admin_init', $position );
    add_action('admin_menu', 'set_admin_menu_separator');

    One thing I thought I’d mention is that using this code in conjunction with some themes + bbPress causes weird “”Fatal error: Allowed memory size exhausted” errors on any menu items that are custom post types from the admin dashboard (i.e. posts). This is occurring using either my above revised code or the original article code.

    Not exactly sure why it happens but thought it would be worth mentioning. One way I have found around it (if changing the theme isn’t an option) is to increase the WP Memory Limit above 64MB and it seems to resolve the issue.

    • Tom McFarlin

      The memory size exhausted may be because of the version of PHP – that’s when I typically see that error.

      Either way, thanks a lot for sharing this as I’m sure it’ll help others!

  11. bozlu

    Awesome post. I am looking this type of post for long time. However i think if is better to use code without plugins because this is a very simple code for add separator to wordpress menu.

    • Tom McFarlin

      Either going the plugin route or the `functions.php` route is fine – it all depends on the use case.

  12. Steve

    Hi, I tried your plugin using wp 3.5 and it did upload ok but when I activate it I get a internal server error. Then i’m unable to access my admin area anymore.

    I did delete the plugin and tried it again. Same issue.

    Please advise?

    • Tom McFarlin

      Since this is a free plugin, I’ll try to take a look at it when I have a chance, but it’s not currently on my list of priorities.

      The best advice I can give right now is to look at the line on which the error appears to be reporting and then attempt to fix it from there.

  13. Kevin van Luijtelaar

    After installing your plugin i get this message on my website.

    Fatal error: Call to undefined function add_settings_section() in /home/ on line 410

    What can i do about this?

    I can’t do anything on my website at the moment.

    Greetings Kevin

    • Tom

      Hey Kevin,

      Unfortunately, based on the error that you’ve laid out above, the error is being thrown by your theme – not the plugin.

      The function is also a proper WordPress function. Beyond that, I don’t have much to offer.

  14. Xavier

    Thank you.

    Another very simple way:

    In the menu editor, below “CSS classe (optional)”, add “mySeparator” or whatever you want beginning with “my” in order to be sure you wont mess up the CSS.

    Go to Appearance –> Editor –> select a CSS, the one you prefer (basically, style.css).

    Add this line where you want inside the body of the file, beginning a new line just after a closing curly brace:

    .mySeparator {border-bottom: black solid 1px;}

    Or change size, color and mode (dotted…..)

    You’re done

Leave a Reply

© 2020 Tom McFarlin

Theme by Anders NorenUp ↑