In the Settings menu, I have the following menu items listed:

-- General
-- Writing
-- Reading
-- Discussion
-- Media
-- Permalinks
-- Blogging

I'd like to the Blogging (options-general.php?page=blogging) reordered underneath General instead of being at the bottom. This was added with the add_options_page() function.

From doing some research, this is what I've come up with:

add_filter( 'custom_menu_order', array( $this, 'submenu_order' ) );
function submenu_order( $menu_order ) {
    global $submenu;
    $order = array();
    $order[] = $submenu['options-general.php'][10];
    $order[] = $submenu['options-general.php'][41];
    $submenu['options-general.php'] = $order;
    return $menu_order;

This works, but it only shows General and Blogging, the rest are removed:

-- General
-- Blogging

Also, $submenu['options-general.php'][41] is currently index position 41 for me. Does this mean it will be the same index position for everyone else even if they have another plugin settings listed?

2 Answers 2


Got it, thanks to cjbj's help, I was able to get the final solution:

add_filter( 'custom_menu_order', 'submenu_order' );
function submenu_order( $menu_order ) {
    # Get submenu key location based on slug
    global $submenu;
    $settings = $submenu['options-general.php'];
    foreach ( $settings as $key => $details ) {
        if ( $details[2] == 'blogging' ) {
            $index = $key;
    # Set the 'Blogging' menu below 'General'
    $submenu['options-general.php'][11] = $submenu['options-general.php'][$index];
    unset( $submenu['options-general.php'][$index] );
    # Reorder the menu based on the keys in ascending order
    ksort( $submenu['options-general.php'] );
    # Return the new submenu order
    return $menu_order;

The result you get is not surprising, given that you're directly manipulating a global variable. You're replacing $submenu with only the items with keys 10 and 41. If you want to follow this method you would need to do this (assuming there's nothing at key 11):

$submenu['options-general.php'][11] = $submenu['options-general.php'][41];
unset ($submenu['options-general.php'][41]);

However, note that you are not using the filter function in any way. Nothing happens to the $menu_order that you are passing through the filter. So, this cannot be a very clean solution.

As you doubtlessly have seen, add_submenu_page simply adds a new submenu at the end of the array in the order the function is called. This line:

$submenu[$parent_slug][] = array ( $menu_title, $capability, $menu_slug, $page_title );

So if a new plugin would call add_submenu_page before you do, the key of 41 could easily be a different one. To prevent this you would have to loop through $submenu to find the correct key. Quick 'n dirty version:

for ( $i = 1; $i <= 100; $i++ ) {
    if ( array_key_exists( $submenu['options-general.php'][$i] ) ) {
        if ( $submenu['options-general.php'][$i][2] == 'my-custom-slug' ) {
            $the_desired_key = $i;
            // [2] because that is the index of the slug in the submenu item array above


Better version of the latter loop:

$sub = $submenu['options-general.php'];
foreach ( $sub as $key => $details ) {
        if ( $details[2] == 'my-custom-slug' ) {
            $the_desired_key = $key;

After finding $the_desired_key in this way, you can safely use the set+unset method above. I've verified 11 is an unused offset.

  • Thanks for the explanation. I updated my question slightly explaining that this custom menu is created from my plugin settings and with your solution, but I get an error for an unexpected '{'. What am I doing wrong? Sep 19, 2016 at 13:32
  • unexpected '{' sounds like basic PHP debugging. add_options_page() is just an alias for add_submenu_page, so that shouldn't make any difference.
    – cjbj
    Sep 19, 2016 at 13:46
  • Updated my answer with the code, just fixing the bug based on the error. May you verify that the rest of the new code is correct? Sep 19, 2016 at 13:56
  • You are now incorporating both the solutions that I suggested. The first with a simple set+unset and the second one, which should be more foolproof.
    – cjbj
    Sep 19, 2016 at 13:59
  • Oh I see, sorry about that. I'm a little bit confused on how this should look. It looks like the second approach is the best since the key could be different on each WordPress install. May you elaborate more on that solution? Sep 19, 2016 at 14:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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