By Greg Nowak. Updated 1 September 2026.
A headless WordPress site should not need hard-coded navigation. If editors manage the pages in WordPress, letting them manage the primary and footer menus there usually makes sense too.
But once navigation is delivered as JSON, it becomes part of your public application contract. Menu labels, URLs and hierarchy can reveal planned services, campaign pages, account routes, partner areas or internal terminology—even when the linked pages themselves remain protected.
This does not make public menus inherently dangerous. It means the agency or delivery team should decide what is public, test that decision anonymously and record it before launch.
What changed in WordPress 6.8?
WordPress 6.8 introduced the rest_menu_read_access filter. The hook remains in the current WordPress developer reference and applies to the controllers for menus, menu items and menu locations.
By default, these resources are not available for anonymous reading. A developer can opt in and can use the request and controller passed to the filter to grant access selectively. This broad example from the WordPress developer note exposes all three menu-related resources:
add_filter( 'rest_menu_read_access', '__return_true' );That line is convenient, but it is a policy decision disguised as a small implementation detail. Use it only when every menu resource and its public fields have been reviewed and are genuinely intended for anonymous access.
Start with the front end’s actual data requirement
Map the requests the production application makes. A typical front end may first resolve a location such as primary, obtain its menu ID and then request the associated items. Exposing only the location endpoint may therefore be too narrow, while exposing every menu and every item may be unnecessarily broad.
The right rule follows the fetch sequence. Identify the locations the application needs, the item collection it reads and any fields added by plugins or custom code. Then implement conditional logic in rest_menu_read_access around those specific requests. Keep that logic in a small custom or must-use plugin so it is not silently lost when the theme changes.
| Launch decision | What to verify | Expected evidence |
|---|---|---|
| Public routes | Menus, items and locations required by the production front end | An explicit route list, not “the REST API is open” |
| Public content | Labels, URLs, hierarchy, descriptions, metadata and custom fields | A reviewed anonymous JSON response |
| Failure behaviour | Restricted routes and unapproved menu IDs | A repeatable 401 or 403 response |
| Browser access | Production, preview and old development origins | A documented CORS decision |
| Cache lifecycle | CDN, host, application and framework caches | Known TTLs and a tested purge path |
| Ownership | Who approves new locations, fields and integrations | A named owner and deployment note |
Test as an outsider, not as an administrator
Run the launch checks without WordPress cookies, authorization headers or preview credentials. Replace the host and menu ID below with production-like values:
curl -i https://cms.example.com/wp-json/wp/v2/menu-locations/primary
curl -i 'https://cms.example.com/wp-json/wp/v2/menu-items?menus=MENU_ID&context=view'
curl -i https://cms.example.com/wp-json/wp/v2/menusRecord the status code, response headers and body. Approved requests should return only content the business is comfortable publishing. Unapproved collections, locations and IDs should remain inaccessible. Repeat the tests against the real production configuration because hosting rules, plugins and deployment variables can differ from staging.
WordPress also supports the global _fields parameter, which lets the front end request a smaller response. For example, it can ask only for the item fields it renders. This improves payload size and makes the consumer’s needs clearer, but it is not access control: another caller can omit _fields. Review the complete anonymous response, not only the front end’s preferred query.
Review the menu as publishable content
A technical permission check is only half the job. Ask the content or operations owner to inspect the returned navigation shortly before launch. Look for staging domains, placeholder links, unpublished campaign names, obsolete support routes and internal descriptions. Check plugin-added metadata as carefully as the visible label.
Also test edits. Remove or rename a harmless item, publish the change and measure how long the old JSON survives. Navigation may be cached by WordPress, the host, a CDN, the front-end framework or several layers at once. The team needs a practical purge method before an urgent correction is required.
CORS does not make public menu data private
WordPress intentionally allows public REST endpoints to be accessed from any site by default; its REST documentation explains that WordPress relies on nonces for CSRF protection rather than strict origin verification. CORS mainly controls which browser scripts can read a cross-origin response. It does not stop direct requests, server-side clients or command-line tools.
Review CORS because overly broad or stale configuration is still poor operational hygiene, but do not use it as the confidentiality boundary for menu data. If navigation truly must remain private, keep the endpoint authenticated and fetch it through a trusted server-side component. Never place an Application Password or another reusable secret in browser JavaScript.
Leave the next team a clear rule
The most useful handover is short and testable: which menu locations are public, which routes support them, who may change the policy, how anonymous access is tested and how cached responses are purged. That turns a one-line filter into an owned part of the system rather than launch-day folklore.
If your agency needs an independent pair of eyes, Greg can review the anonymous REST surface, menu contents, cache path and deployment notes without turning the exercise into a heavyweight security programme. Talk to Greg about a practical pre-launch review.
Related on GrN.dk
- Can’t Publish in WordPress? Fix the Invalid JSON Response Without Guesswork
- Before Your Support Bot Learns the Help Center, Test Whether It Can Forget
- Cloudflare Resource Tagging: Small Labels, Real Governance Consequences
Need help with this kind of work?
Plan a pre-launch review with Greg Get in touch with Greg.