If regular pages and posts load fine but specifically your custom post type items — WooCommerce products, portfolio entries, listings, or another CPT registered by a plugin or theme — return a 404, this is a narrower problem than a general permalink break. It's almost always either a rewrite rules cache that needs a proper flush, or a slug collision between the CPT's URL base and an existing page. Both are fixable without touching server config.
Symptoms
- Regular posts and pages load normally; only URLs belonging to one specific custom post type (e.g.,
/product/some-item/or/portfolio/some-project/) 404. - This started right after changing the site's permalink structure, activating/deactivating a plugin that registers the CPT, or renaming a page.
- The CPT's archive page (e.g.,
/product/or/portfolio/) may also 404, or may load but individual items within it don't. - Editing the item in wp-admin and clicking "View" takes you to a URL that then 404s on the front end.
Likely causes, in order of how often they actually happen
- Rewrite rules weren't properly flushed after the permalink or plugin change. WordPress caches all registered rewrite rules (including every CPT's URL patterns) in the database. Simply changing a setting in
wp-config.php, deactivating/reactivating a plugin via WP-CLI, or restoring a database backup does not automatically regenerate this cache — it needs an explicit trigger. - A slug collision between the CPT's rewrite base and an existing page. If a static WordPress page exists with the same slug as the custom post type's base (for example, a page literally titled/slugged "portfolio" while the CPT is also registered with the base
portfolio), WordPress's URL matching can favor the page over the CPT's rewrite rules, causing individual CPT items underneath that base to 404. This is a very common, easy-to-miss cause with plugin-registered CPTs like WooCommerce (base:product) or portfolio/listing plugins. - The CPT's own
rewriteregistration argument is misconfigured, if it's a custom-coded post type (in a custom plugin or in the theme's functions.php) rather than one from an established plugin. A typo in theslugvalue,'rewrite' => falseset unintentionally, or the CPT not being marked'public' => truewill all produce exactly this symptom.
Fix it: lowest-risk steps first
Step 1 — Flush permalinks properly through the WordPress admin (not by editing files)
This is the correct way to force WordPress to regenerate its rewrite rules, and it's non-destructive — it doesn't change your chosen URL structure, just rebuilds the underlying rule cache.
- Go to Settings → Permalinks in wp-admin.
- Without changing the selected structure, click Save Changes.
Do not try to "flush" by manually deleting rows from the database or editing wp-config.php — there's no supported constant for this, and the safe, standard method is exactly this admin action, which WordPress core explicitly hooks into to rebuild the rewrite rules.
If you manage the site via WP-CLI, the equivalent command is wp rewrite flush, which does the same thing without needing to log in to wp-admin.
Step 2 — Check for a slug collision using Query Monitor
If flushing permalinks didn't fix it, the next most common cause is a slug collision, and the cleanest way to confirm it is with the free Query Monitor plugin rather than guessing.
- Install and activate Query Monitor from the WordPress plugin directory (Plugins → Add New → search "Query Monitor").
- Visit the 404'ing CPT URL on the front end with the admin toolbar visible.
- In the Query Monitor bar (bottom of the screen), open the Query Monitor panel and check the Request tab — it shows which template WordPress decided to load and why, including whether it matched a
pageobject instead of the expected CPT single template. - Separately, go to Pages in wp-admin and check whether any existing page has the same slug as the CPT's rewrite base (visible in the page's row under the title, or by checking Quick Edit).
If you find a matching page slug, you've confirmed the collision. Temporarily rename the conflicting page's slug (Quick Edit → Slug field) to something else, save, then re-flush permalinks (Step 1 again) and retest the CPT URL. If that resolves it, either keep the page renamed permanently or, if you specifically need that exact slug for the page, change the CPT's rewrite base instead (via the plugin's settings if it has one, or via code if it's a custom CPT — see Step 3).
Step 3 — If it's a custom-coded CPT: verify the rewrite argument in its registration
If the post type isn't from a well-known plugin (WooCommerce, a page builder, etc.) but is registered by a custom plugin or in the active theme's functions.php, the issue may be in the register_post_type() call itself rather than a caching or collision issue.
- Locate the
register_post_type()call for the CPT (search the theme/plugin files for the CPT's key, e.g.,'portfolio'). - Confirm the arguments array includes
'public' => trueand either no'rewrite'key at all (which defaults sensibly) or an explicit'rewrite' => array('slug' => 'your-desired-slug')with the slug spelled exactly as expected — a typo here is enough to cause every single item to 404 while the CPT still works fine in wp-admin. - After correcting the code, re-flush permalinks (Step 1) — code changes to a CPT's registration require a fresh flush to take effect, same as any other rewrite change.
Only edit this file if you or your developer maintain the code directly; if the CPT comes from a third-party plugin, update the plugin instead of hand-editing its files, since plugin updates will overwrite direct edits anyway.
Verify the fix
- Visit the CPT's archive URL (e.g.,
/product/) and confirm it lists items correctly. - Visit at least two individual CPT item URLs directly and confirm they load the correct single-item template, not a 404.
- If you renamed a page to resolve a collision, confirm that page still works correctly at its new slug and that any menu links or internal links pointing to its old URL are updated.
- Re-run Query Monitor's Request tab check on a previously-broken URL to confirm it now correctly identifies the CPT template as the matched query.
If you need to roll back
If renaming a page's slug breaks other things (menu links, existing backlinks, or forms tied to that exact URL), simply Quick Edit the slug back to its original value and flush permalinks again — nothing is destroyed by this process since slugs and rewrite rules are just settings, not content. If a code change to a custom CPT's registration causes new problems, revert the register_post_type() arguments to their previous state and flush permalinks once more.
Related fixes
- Fix WordPress 404 Errors After Changing Permalinks — If regular pages are 404ing too, not just custom post types.
FAQ
Do I need to flush permalinks every time I add a new custom post type?
Yes, once — right after the CPT is registered and active (via Settings → Permalinks → Save, or wp rewrite flush). WordPress doesn't automatically detect newly registered post types and update the rewrite cache; you have to trigger it once per registration or registration change.
Why does the CPT item work fine when I preview it from wp-admin but 404 on the live URL?
The "Preview" and "View" links in wp-admin sometimes use a query-string-based URL that bypasses the pretty-permalink rewrite rules entirely, so they can succeed even when the actual public rewrite rule is broken. Always test the plain public-facing URL (not a preview link) when diagnosing this.
Could a caching plugin be causing this instead?
It's possible if the CPT worked before and started 404ing without any permalink or plugin change — in that case, purge your page cache first before assuming it's a rewrite issue. But if the timing lines up with a permalink structure change, a plugin update, or a new page being created, the causes above are far more likely.
Is it safe to change a CPT's rewrite slug on a live site?
Changing the rewrite slug changes every URL for that post type going forward, which will break any existing external links, bookmarks, or search engine listings pointing to the old URLs unless you also set up redirects from the old slug pattern to the new one. Only change it if you understand that trade-off, and prefer renaming the conflicting page instead when that's an option.
