Prevent page slug from interfering with custom post type archive permalink?

In my plugin, I’m creating a custom post type that has an archive and uses the rewrite parameter to choose a slug:

$args = array (
    'public' => true,
    'has_archive' => true,
    'rewrite' => array('slug' => 'lessons', 'with_front' => false)
register_post_type ('my_plugin_lesson', $args);

With this code, I get an archive of the ‘my_plugin_lesson’ posts at

However, I can also create a Page with the title “Lessons”, and WordPress will assign it the slug ‘lessons’. The View Page link on the admin panel for this page will be, but the page is inaccessible because WordPress shows the archive for my custom post type.

Is there a way to prevent WordPress from choosing a slug for pages or posts that interfere with the slug of my custom post types?

2 Answers

I know you already find the solution, because you figured out this question was already asked and answered…

However, once this question is a bounty one and cannot be closed as duplicate, I want to make sense to this, and add (a bit of) additional value on the answers already posted.

What is done in those answers (the one by @RachelCarden and the one posted here by @matthew-boynes) is what you need, but it is hardcoded, i.e. you have to manually add the cpt archive slug in the filter function.

What I want to obtain is a filter that works in the same way, but dinamically, something like:

add_filter('wp_unique_post_slug_is_bad_hierarchical_slug', 'prevent_slug_override', 99, 3);
add_filter('wp_unique_post_slug_is_bad_flat_slug', 'prevent_slug_override', 99, 3);

function prevent_slug_override( $now, $slug, $type ) {
  if ( in_array($type, array('nav_menu_item', 'revision', 'attachment') ) )
    return $now;
  $cpts = get_post_types( array('_builtin' => false), 'objects' );
  $check_bad = array();
  foreach ( $cpts as $cpt => $object ) {
    if ( $object->has_archive ) {
      if (
        ! empty($object->rewrite) && isset($object->rewrite['slug']) &&
        ! empty($object->rewrite['slug']) && ($object->rewrite['slug'] != $cpt)
      ) {
        $check_bad[] = $object->rewrite['slug'];
      } else {
        $check_bad[] = is_string($object->has_archive) ? $object->has_archive : $cpt;
  if ( in_array($slug, $check_bad) ) return true;
  return $now;

Leave a Comment