Custom Post Type rewrite rules not working, how to alter the rewrite order?

I’ve gone through four or five Codex pages, a dozens Stackexchange pages, and four or five dev blogs trying to solve this.

I have a custom post type called authors. The CPT is set up correctly, I pulled the CPT framework from the Codex, and I’ve checked it against a CPT generator. The Admin screens create, edit, and list Author posts correctly, and the URLs are in the correct format of Author archives also work.

But nothing I do will display that page on the front end. It’s nothing but 404s.

I have flushed the rewrite a dozen times though the Permalinks page and WP-CLI. I have also installed Custom Post Type Permalinks and have the same issue.

CPT is here:

add_action( 'init', 'to4_cpt_author' );

function to4_cpt_author() {
  $labels = array(
    'name'               => _x( 'Authors', 'post type general name' ),
    'singular_name'      => _x( 'Author', 'post type singular name' ),
    'menu_name'          => _x( 'Authors', 'admin menu' ),
    'name_admin_bar'     => _x( 'Author', 'add new on admin bar' ),
    'add_new'            => _x( 'Add New', 'author' ),
    'add_new_item'       => __( 'Add New Author' ),
    'new_item'           => __( 'New Author' ),
    'edit_item'          => __( 'Edit Author' ),
    'view_item'          => __( 'View Author' ),
    'all_items'          => __( 'All Authors' ),
    'search_items'       => __( 'Search Authors' ),
    'parent_item_colon'  => __( 'Parent Authors:' ),
    'not_found'          => __( 'No authors found.' ),
    'not_found_in_trash' => __( 'No authors found in Trash.' )

  $args = array(
    'labels'             => $labels,
    'description'        => __( 'Author post type.' ),
    'public'             => true,
    'publicly_queryable' => true,
    'show_ui'            => true,
    'show_in_menu'       => true,
    'query_var'          => 'author',
    'rewrite'            => array('slug' => 'author', 'with_front' => true ),
    'capability_type'    => 'post',
    'has_archive'        => true,
    'hierarchical'       => false,
    'menu_position'      => 9,
    'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ),
    'taxonomies'         => array( 'admin_tag', 'post_tag' ),
    'menu_icon'          => 'dashicons-id-alt'


  register_post_type( 'author', $args );

I’ve installed the Query Monitor plugin and it tell me the following rewrites are matched.

All Matching Rewrite Rules
Rule                            Query
([^/]*)/([^/]*)/?$              post_type=post

^author/([^/]*)/?               post_type=author

author/([^/]+)(?:/([0-9]+))?/?$ author=$matches[1]

(.?.+?)(?:/([0-9]+))?/?$        pagename=$matches[1]

The second query ^author/([^/]*)/? is one I wrote using:

function to4_rewrite_rule() {
    add_rewrite_rule( '^author/([^/]*)/?', 'index.php?post_type=author&name=$matches[1]','top' );
add_action('init', 'to4_rewrite_rule', 10, 0);

But the top query seems to be getting matched first, which is why I am getting 404s. Query Monitor shows the generated query as name=catherine-collins&post_type=post when it should be name=catherine-collins

Where is that query coming from and how do I demote it? There’s no other add_rewrite_rule anywhere in my theme or any plugin so I am guessing this is core. The theme is Twenty-Seventeen with my own custom plugin defining several Custom Post Types.

Solutions Collecting From Web of "Custom Post Type rewrite rules not working, how to alter the rewrite order?"

While you’ve found the problem already, here is some basic code to move a specific rule to the end of the rewrite rules with the rewrite_rules_array filter for people stumbling across a similar problem.

Say I want to move the rule for ([^/]*)/([^/]*)/?$:

add_filter("rewrite_rules_array", function($rules) {
    $keys = array_keys($rules);
    foreach($keys as $rule) {
        if($rule == '([^/]*)/([^/]*)/?$') {
            $value = $rules[$rule];
            $rules[$rule] = $value;
    return $rules;

Note that you’ll need to flush/update the rules for this to have any effect. Saving your permalinks configuration in the backend is enough to accomplish that.

Yeah so it was user error after all.

Months ago I had experiment with Yeost and had added a rewrite_rules_array is functions.php. It was taking priority over the add_rewrite_rule. Once I deleted that, custom posts types worked normally.

Here’s the offending code:

add_filter( 'rewrite_rules_array', 'my_rewrite_rules_array');
function my_rewrite_rules_array($rules) {
    $rules = array('([^/]*)/([^/]*)/?$' => 'index.php?post_type=post&name=$matches[2]&meta=$matches[1]') + $rules;
    return $rules;

This was solved by using Atom to do a non-grep search for '([^/]*)/([^/]*)/?$' on the whole site folder.