Skip to content
  • FG-87
  • 14 min read
  • Last checked November 7, 2026

How to Fix WordPress Menu Items Not Saving or Disappearing on Large Menus

You click Save Menu and the last items vanish or never stick. Find out whether PHP is cutting off the form, a script is failing, or the save is timing out, and fix it.

Run the health check

You add a batch of items under Appearance, Menus, click Save Menu, see “Main Menu has been updated”, and then the bottom of the menu is gone. Sometimes new items never stick, sometimes nested items pop back to the top level, and sometimes a whole branch of the menu disappears from the front end. It almost always happens on menus with roughly 80 or more items, and it gets worse as the menu grows.

Under the hood, the menu screen is one big HTML form. Every menu item sends more than a dozen separate fields to the server. PHP has a safety limit, max_input_vars, that stops reading form fields after a set number (1000 by default) and silently throws the rest away. WordPress then saves what it received and treats the missing items as deleted. This guide shows you how to prove that is what happened, fix the limit properly, and deal with the other causes that look the same: broken scripts on the Menus screen, timeouts on very large saves, and block theme menus that live somewhere else entirely.

What is happening

When you save a classic menu, your browser posts the update-nav-menu form to wp-admin/nav-menus.php. For each item, the form carries fields named like menu-item-db-id[ITEM_ID], menu-item-title[ITEM_ID], menu-item-url[ITEM_ID], menu-item-parent-id[ITEM_ID], menu-item-position[ITEM_ID] and so on. Current WordPress core sends about 15 of these per item: database ID, object ID, object, parent ID, position, type, title, URL, description, title attribute, target, CSS classes, XFN and a couple of ordering fields. Plugins that add custom fields to menu items (icons, mega menu settings, visibility rules) add more.

That is why trouble starts at around 70 to 100 items: 1000 divided by 13 to 15 fields is roughly 66 to 77 items, and plugin fields lower it further.

Setting Default What it limits Where it can be set
max_input_vars 1000 Number of GET, POST and cookie variables PHP will parse per request php.ini, .user.ini, pool config, .htaccess under mod_php or LiteSpeed. Not with ini_set()
post_max_size 8M in stock PHP Total size of the POST body Same places
max_execution_time 30 seconds in stock PHP How long the save may run Same places, plus the web server and proxy timeouts
suhosin.post.max_vars, suhosin.request.max_vars Set by the Suhosin config A second, separate variable cap on old servers running the Suhosin extension suhosin.ini or php.ini

There is an important detail most guides miss. Since WordPress 4.5.3, the Menus screen has its own workaround: when you click Save, a script copies the whole form into a single hidden field called nav-menu-data as JSON. That field is the first input in the form, so PHP always keeps it, and WordPress rebuilds the full list from it before saving. On a healthy modern site, a 300 item menu can save with the default limit of 1000.

So when you still lose items today, one of these is true: the JavaScript on the Menus screen did not run (a script error, a plugin replacing the screen, or a browser extension), you are on a very old WordPress version, a plugin saves menu data through its own form or AJAX request without the workaround, Suhosin or a firewall is truncating the large JSON value, or the save is running out of time halfway through. The steps below test each one.

Before you start

You need a WordPress administrator login. For the server checks you need either your hosting control panel (for PHP settings and the error log) or SSH access. Before changing anything, take a backup of the database, or at least export the menu as described in Step 1, because a failed save on a big menu deletes items for real.

How to run commands in this guide:

  • Server commands run in an SSH terminal, from the site folder (the one that contains wp-config.php). Your host may call the WP-CLI tool wp or wp-cli; use whichever exists.
  • Browser checks use the developer tools in Chrome, Edge or Firefox. Press F12 (or Cmd+Option+I on a Mac) and use the Console and Network tabs.
  • Config files such as .user.ini are files you edit in the site folder with SFTP, the file manager in your panel, or a terminal editor.

Step 1: Count the menu and save a copy of it

First find out how big the menu really is, and take a copy you can rebuild from.

wp menu list --fields=term_id,name,slug,locations,count
wp menu item list MENU_SLUG --format=count
wp menu item list MENU_SLUG --fields=db_id,menu_item_parent,position,type,object,object_id,title,link --format=csv > menu-backup-YYYY-MM-DD.csv

Run these on the server over SSH, in the site folder.

  • MENU_SLUG is the menu’s slug from the first command’s slug column (for example main-menu). You can also use the menu’s name in quotes or its term_id.
  • YYYY-MM-DD is today’s date, so you know which copy is which.
  • --fields picks the columns to show. count in the first command is the number of items in each menu, and locations shows which theme location uses it.
  • --format=count prints only the number of items. --format=csv with > writes the list to a file.

Good output is a count that matches what you expect. If it is already lower, the damage has been saved and the CSV records what is left, not what was lost, so look for a recent database backup before rebuilding by hand. If the menu has fewer than about 60 items, max_input_vars is unlikely to be your cause and you should jump to Step 4.

Step 2: Confirm that PHP is dropping form fields

Look for the warning in the PHP error log

When PHP hits the limit, it writes a warning like this:

PHP Warning:  Unknown: Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0

This is an example log line, not a command. The number after “exceeded” is the limit in force for that request. “Unknown on line 0” means PHP raised it while reading the request, before any WordPress code ran.

Because it happens before WordPress loads, this warning usually lands in the server’s PHP error log, not in wp-content/debug.log. Look in your panel’s error log page, or search the log over SSH:

grep -i "input variables exceeded" PHP_ERROR_LOG_PATH | tail -n 20
  • PHP_ERROR_LOG_PATH is the path of the PHP error log. Find it in your hosting panel, or run wp eval 'echo ini_get("error_log");', which shows the CLI value (often the same as the web value, but not always). Common places are a logs folder in your home directory or an error_log file in the site folder.
  • -i makes the search ignore upper and lower case. tail -n 20 shows the last 20 matches.

A match with a timestamp that lines up with your failed save confirms the cause. No match does not fully rule it out, because some hosts do not log warnings. Check the next two items as well.

Check the effective limit for web requests

Go to Tools, Site Health, Info, open the Server section, and read PHP max input variables. This is the value PHP uses for real page loads, which is what matters. Do not trust php -i on the command line: the CLI often reads a different php.ini from the one your website uses.

Check whether the menu screen sent the JSON copy

Open the Menus screen, open developer tools, and select the Network tab. Click Save Menu. Click the nav-menus.php POST request and open the Payload (Chrome) or Request (Firefox) tab. You should see a nav-menu-data field with a long JSON string. If it is empty or missing, the save script did not run, and PHP fell back to the plain form, which is where the limit bites. Switch to the Console tab and look for red errors on page load; Step 4 covers those.

Step 3: Raise max_input_vars the right way

max_input_vars is a per-directory setting. That means you can set it in php.ini, in a .user.ini file, in the PHP-FPM pool, or in .htaccess on some servers, but not with ini_set() in wp-config.php or a plugin. By the time PHP runs that code, the form has already been read.

Option A: your hosting panel

Most panels have a PHP settings or PHP options page where you can set max_input_vars per site. Set it to 5000 for a big menu, save, and recheck Site Health. This is the cleanest route because the panel writes the setting where your PHP version actually reads it.

Option B: a .user.ini file (PHP-FPM, FastCGI, CGI)

max_input_vars = 5000

Put this line in a file named .user.ini in the site folder (next to wp-config.php). If the file already exists, add the line rather than replacing the file.

  • 5000 is a sensible value for menus with up to a few hundred items. Go to 10000 only if you have a reason; very high values make the server do more work on hostile requests.
  • PHP rereads .user.ini only every user_ini.cache_ttl seconds (300 by default), so wait five minutes or restart PHP from your panel.

Good result: Site Health shows the new number. Bad result: it still shows 1000, which means your server does not read .user.ini (for example, Apache running PHP as a module). Use Option A, C or D instead.

Option C: .htaccess on mod_php or LiteSpeed only

php_value max_input_vars 5000

Add this to .htaccess in the site folder, outside the # BEGIN WordPress block. It only works where PHP runs as an Apache module or under LiteSpeed. On a PHP-FPM server it causes a 500 Internal Server Error on every page, so check the site straight after saving and remove the line if that happens.

Option D: the PHP-FPM pool or php.ini (servers you manage)

php_admin_value[max_input_vars] = 5000

This goes in the site’s PHP-FPM pool file (the path depends on your distribution and PHP version, for example a file under /etc/php/PHP_VERSION/fpm/pool.d/). PHP_VERSION is the version number folder, such as the one your site runs. php_admin_value sets the value so site code cannot override it. Reload PHP-FPM afterward.

Suhosin on old servers

Suhosin is a hardening extension that was common on PHP 5 era servers and still turns up on old builds. It has its own variable caps that apply on top of max_input_vars, plus a cap on the length of a single value, which can cut off the large nav-menu-data JSON string. If Site Health or phpinfo() shows a Suhosin section, raise its limits too:

suhosin.post.max_vars = 5000
suhosin.request.max_vars = 5000

These lines go in php.ini or the Suhosin ini file on the server. post.max_vars caps POST fields and request.max_vars caps all request variables combined; both must be raised. Suhosin also logs its own messages (they mention “suhosin” and “dropped variable”), so search the logs for that word. A server still running Suhosin is almost certainly on an unsupported PHP version, so upgrading PHP is the real fix.

Rebuild and test

After the limit is raised and Site Health confirms it, rebuild the lost items using your CSV or backup as the reference. Then add one test item at the very bottom, save, reload the screen, and check it is still there.

Step 4: The Menus screen script is failing

If nav-menu-data was empty in Step 2, or the limit is already high and items still vanish, a JavaScript error is the likely cause. It also produces other clues: you cannot drag items, the item panels do not open, or “Add to Menu” does nothing.

  1. Open the Console tab in developer tools and reload the Menus screen. Note any red errors and the file named next to them (for example a file inside /wp-content/plugins/PLUGIN_SLUG/).
  2. Retry in a private window with extensions disabled; script blockers can break admin screens.
  3. Deactivate menu-related plugins one at a time (mega menus, menu icons, menu visibility, admin customizers) and retry.

A plugin named in the console error is your lead. The Health Check and Troubleshooting plugin lets you disable plugins for your own session only, so visitors are not affected. If a speed plugin also optimizes admin scripts, turn that off.

Step 5: The save is timing out on a very large menu

Saving a menu updates every item, and each item is a post with several metadata rows. With hundreds of items, a slow database or plugins hooked into each save, the request can outrun max_execution_time or a proxy timeout. Items processed before the cut-off are saved; the rest end up out of order, under the wrong parent, or missing.

Signs: the save hangs, then ends in a 502, 504 or blank page; or the PHP error log shows “Maximum execution time of 30 seconds exceeded” naming a file in wp-includes/nav-menu.php or a plugin. Raise max_execution_time to 120 for the admin (the same places as in Step 3), retry, and seriously consider making the menu smaller (see Step 7). FG-13 covers gateway timeouts in depth.

Firewall limits on the request size

A firewall or ModSecurity rule may refuse a large request body. That shows as a 403, 406 or 413 on the nav-menus.php POST in the Network tab; FG-49 covers security rule blocks.

Step 6: Block themes store menus somewhere else

If your theme is a block theme (Appearance shows “Editor” instead of “Customize” and “Menus”), the header menu is probably a Navigation block, not a classic menu. Navigation block menus are stored as posts of the wp_navigation post type, and the editor saves them through the REST API as one JSON document. max_input_vars does not apply to them at all.

wp post list --post_type=wp_navigation --fields=ID,post_title,post_status,post_modified
wp post get NAVIGATION_ID --field=post_content | head -c 2000

Run these over SSH in the site folder.

  • The first lists every Navigation menu with its ID and last change. NAVIGATION_ID is an ID from that list.
  • --field=post_content prints the block markup of the menu, and head -c 2000 shows the first 2000 characters so the terminal is not flooded.

Common surprises: the header uses a different Navigation menu from the one you edited, the block still renders a fallback Page List, or you are editing a classic menu that was imported into a Navigation menu long ago. In the Site Editor, open the header template part, select the Navigation block, and use its menu options to see which menu it displays, switch menus, or use Import classic menus. If saving fails with a message about a JSON response, the REST API is the problem, which FG-21 covers.

Step 7: Alternatives to one giant menu

A header menu with hundreds of links is hard to save and hard for visitors to use. Once saving works, consider:

  • Split by location. Keep the header menu to the main sections and move long lists into footer menus or a secondary menu.
  • Use landing pages. Replace a long sub-menu with one link to a page that lists those items.
  • Use a mega menu built from content. Mega menu plugins and page builder menu elements let you place a block of content (columns, headings, links, images) inside a single menu item, so the menu itself stays small. Check how the plugin stores its settings: some add extra fields to every item, which makes the input limit worse, not better.

Advanced: Measuring how many variables your save sends

If you want hard numbers instead of estimates, count the fields the form actually posts. Open the Menus screen, open the browser console, and paste this:

document.querySelectorAll('#update-nav-menu [name]').length

This runs in the browser console on the Menus screen. It counts every named field in the save form, which is the number of variables PHP must parse if the JSON workaround is not used. There are no placeholders.

Compare the result with your limit. A result of 1400 with a limit of 1000 means about 400 fields, the last 25 to 30 items, will be dropped by a plain form post. Then count fields per item to see whether a plugin is inflating them:

document.querySelectorAll('#update-nav-menu [name$="[MENU_ITEM_ID]"]').length

This also runs in the browser console. MENU_ITEM_ID is the ID of one item; find it by hovering over an item’s panel or from the db_id column in Step 1. Core on its own gives about 15. A number like 25 or 30 means a plugin adds its own fields, and you can use the result to set a limit with headroom: items multiplied by fields per item, plus 100, rounded up.

Cookies and query string variables count toward the same limit, and other heavy admin forms (theme option panels, product variation screens) hit it the same way, so fix the limit at the server level rather than working around it.

When to contact your host

Open a ticket if you cannot change PHP settings yourself, if Site Health does not show the new value after you changed it, or if saves fail with 403, 413, 502 or 504 errors. Include:

  • The UTC time of a failed save, so they can match it to their logs.
  • The current value of “PHP max input variables” from Site Health and the value you want (for example 5000).
  • Any log line you found, such as the “Input variables exceeded” warning, a Suhosin message, a timeout or a firewall rule ID.
  • The HTTP status code of the nav-menus.php POST from the Network tab.
  • The number of items in the menu and what changed recently (a PHP version change, a new menu plugin, a server move).

Not sure where your problem fits? Try our diagnostic tools or the Emergency Room.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close