How to query 2 custom post types that need to share a slug?

I'm trying to make 2 custom post types (authors and works) share the same slug. The authors CPT uses an archive page which functions properly, but works CPT does not need one.

CPT 1: https://domain.com/library/authors/author-name/

CPT 2: https://domain.com/library/authors/author-name/works-name/

The permalink rewrite I cobbled together is functional. It pulls the author post ID from a custom meta menu in the works CPT post and then builds the permalink. However, I get a 404 error when visiting a works link because WordPress can't find the works post in the database due to the sharing of slugs.

What I can't figure out is how to query the works post so it can go around usual query that fails and locate the post. I tried the only 2 examples I found on here and stackoverflow, but they aren't working. I tried changing authors CPT to hierarchical (just in case) but didn't seem to matter. Been at this for days and this type of stuff is way over my head, so maybe I'm just blind to the obvious.

Post Creation Code

////////////////////////////////
//////// Works POST TYPE
function works_post_type() {
    $labels = array(
        'name'                  = _x( 'Works', 'Post Type General Name', 'text_domain' ),
        'singular_name'         = _x( 'Work', 'Post Type Singular Name', 'text_domain' ),
        'menu_name'             = __( 'Works', 'text_domain' ),
        'name_admin_bar'        = __( 'Works', 'text_domain' ),
        'archives'              = __( 'Works Archives', 'text_domain' ),
        'parent_item'           = null,
        'parent_item_colon'     = null,
        'all_items'             = __( 'All Works', 'text_domain' ),
        'add_new_item'          = __( 'Add New Work', 'text_domain' ),
        'add_new'               = __( 'Add New Work', 'text_domain' ),
        'new_item'              = __( 'New Work', 'text_domain' ),
        'edit_item'             = __( 'Edit Work', 'text_domain' ),
        'update_item'           = __( 'Update Work', 'text_domain' ),
        'view_item'             = __( 'View Work', 'text_domain' ),
        'search_items'          = __( 'Search Works', 'text_domain' ),
        'not_found'             = __( 'Works Not found', 'text_domain' ),
        'not_found_in_trash'    = __( 'Works Not found in Trash', 'text_domain' ),
        'featured_image'        = __( 'Featured Image', 'text_domain' ),
        'set_featured_image'    = __( 'Set featured image', 'text_domain' ),
        'remove_featured_image' = __( 'Remove featured image', 'text_domain' ),
        'use_featured_image'    = __( 'Use as featured image', 'text_domain' ),
        'insert_into_item'      = __( 'Insert into Work', 'text_domain' ),
        'uploaded_to_this_item' = __( 'Uploaded to this Work', 'text_domain' ),
        'items_list'            = __( 'Works list', 'text_domain' ),
        'items_list_navigation' = __( 'Works list navigation', 'text_domain' ),
        'filter_items_list'     = __( 'Filter Works items list', 'text_domain' ),
    );
    $args = array(
        'label'                 = __( 'Works', 'text_domain' ),
        'description'           = __( 'Written works', 'text_domain' ),
        'labels'                = $labels,
        'supports'              = array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'custom-fields', 'comments', 'page-attributes'),
        'taxonomies'            = array( 'post_tag' ),
        'hierarchical'          = true,
        'public'                = true,
        'show_ui'               = true,
        'show_in_menu'          = true,
        'menu_position'         = 2,
        'menu_icon'             = 'dashicons-book-alt',
        'show_in_admin_bar'     = true,
        'show_in_nav_menus'     = true,
        'can_export'            = true,
        'has_archive'           = false,
        'exclude_from_search'   = false,
        'publicly_queryable'    = true,
        'query_var'             = true,
        'capability_type'       = 'post',
    );
    register_post_type( 'works', $args );
}
add_action( 'init', 'works_post_type', 0 );
////////////////////////////////
//////// AUTHOR POST TYPE
function authors_post_type() {
    $labels = array(
        'name'                  = _x( 'Authors', 'Post Type General Name', 'text_domain' ),
        'singular_name'         = _x( 'Author', 'Post Type Singular Name', 'text_domain' ),
        'menu_name'             = __( 'Authors', 'text_domain' ),
        'name_admin_bar'        = __( 'Authors', 'text_domain' ),
        'archives'              = __( 'Authors Archives', 'text_domain' ),
        'parent_item'           = null,
        'parent_item_colon'     = null,
        'all_items'             = __( 'All Authors', 'text_domain' ),
        'add_new_item'          = __( 'Add New Author', 'text_domain' ),
        'add_new'               = __( 'Add New Author', 'text_domain' ),
        'new_item'              = __( 'New Author', 'text_domain' ),
        'edit_item'             = __( 'Edit Author', 'text_domain' ),
        'update_item'           = __( 'Update Author', 'text_domain' ),
        'view_item'             = __( 'View Author', 'text_domain' ),
        'search_items'          = __( 'Search Authors', 'text_domain' ),
        'not_found'             = __( 'Authors Not found', 'text_domain' ),
        'not_found_in_trash'    = __( 'Authors Not found in Trash', 'text_domain' ),
        'featured_image'        = __( 'Featured Image', 'text_domain' ),
        'set_featured_image'    = __( 'Set featured image', 'text_domain' ),
        'remove_featured_image' = __( 'Remove featured image', 'text_domain' ),
        'use_featured_image'    = __( 'Use as featured image', 'text_domain' ),
        'insert_into_item'      = __( 'Insert into Author', 'text_domain' ),
        'uploaded_to_this_item' = __( 'Uploaded to this Author', 'text_domain' ),
        'items_list'            = __( 'Authors list', 'text_domain' ),
        'items_list_navigation' = __( 'Authors list navigation', 'text_domain' ),
        'filter_items_list'     = __( 'Filter Authors items list', 'text_domain' ),
    );
    $args = array(
        'label'                 = __( 'Authors', 'text_domain' ),
        'description'           = __( 'Written authors', 'text_domain' ),
        'labels'                = $labels,
        'supports'              = array( 'title', 'thumbnail', 'editor', 'revisions', 'custom-fields'),
        'taxonomies'            = array( 'post_tag' ),
        'hierarchical'          = true,
        'public'                = true,
        'show_ui'               = true,
        'show_in_menu'          = true,
        'menu_position'         = 2,
        'menu_icon'             = 'dashicons-businessperson',
        'show_in_admin_bar'     = true,
        'show_in_nav_menus'     = true,
        'can_export'            = true,
        'has_archive'           = true,        
        'exclude_from_search'   = false,
        'publicly_queryable'    = true,
        'query_var'             = true,
        'rewrite'               = array('slug' = 'library/authors'),
        'capability_type'       = 'post',
    );
    register_post_type( 'authors', $args );
}
add_action( 'init', 'authors_post_type', 0 );
add_theme_support( 'post-thumbnails', array( 'authors' ) );

Permalink Rewrite (works)

add_filter( 'post_type_link', 'my_website_filter_post_type_link', 1, 4 );
function my_website_filter_post_type_link( $post_link, $post, $leavename, $sample ) {
    switch( $post-post_type ) {

        case 'works':

            $author_id = get_post_meta( $post-ID, 'wrks_author', true);
            $author = basename(get_permalink($author_id));
            if ( $author !== '' ) {

                // create the new permalink
                $post_link = home_url( user_trailingslashit( 'library/authors/' . $author . '/' . $post-post_name ) );
            }

            break;

    }
    return $post_link;
}

Query Fix Option 1 (failed)

add_filter('request', 'overwriteQueryVars', 10, 1);

function overwriteQueryVars($query)
{
    if ( empty($query['post_type']) || $query['post_type']!='authors' ) {
        return $query;
    }

    $sql = SELECT ID FROM wp_posts WHERE post_type='authors' and post_name='%s';
    $post_name = urlencode($query['name']);

    if ( in_authors_but_not_works ) {
        return $query;
    }

    $post_name = $query-request;
    $postType = 'works';
    
    $query = array (
        'page' = '',
        'works' = $post_name,
        'post_type' = $postType,
        'name' = $post_name,
    );

}

Query Fix Option 2 (failed)

add_action('parse_request', 'overwriteRequest', 10, 1);
function overwriteRequest($query) {

    if ( count($query-query_vars)  0  empty($query-query_vars['post_type'])) {

        $pageName = preg_replace('/library\/authors\//', '', $query-request);
        $postType = 'works';

        $result = get_posts(array(
            'post_type' = $postType,
            'name' = $pageName
        ));

        if (count($result)  1) {

            return $query;

        } else {
            $new_query = array(
                'page' = '',
                'works' = $pageName,
                'post_type' = $postType,
                'name' = $pageName
            );
            $query-query_vars = $new_query;
            return $query;
        }

    } else {
        return $query;
    }
}

Topic request-filter rewrite-rules permalinks query custom-post-types Wordpress

Category Web


Note that a filter callback must always return something which is commonly the first parameter passed to the callback. So for example your overwriteQueryVars() function, it must always return the $query because request is a filter hook.

Secondly, instead of overwriting the entire query arguments (e.g. by doing $query = array( ... ) or $query->query_vars = $new_query;), you should only change the ones need to be changed in the existing query arguments, e.g. $query['post_type'] = $postType;.

But anyway, try this which worked well for me:

add_filter( 'request', 'overwriteQueryVars' );
function overwriteQueryVars( $query_vars ) { // I renamed $query to $query_vars.
    global $wp;

    /*
     * 1. Check if the current page URL path begins with library/authors/ as in example.com/library/authors/
     * 2. Then check if the post_type is exactly "authors".
     * 3. Finally, check if the "authors" is in the form of <slug>/<slug>.
     */
    if ( preg_match( '#^library/authors/#', $wp->request )        &&
        isset( $query_vars['post_type'], $query_vars['authors'] ) &&
        'authors' === $query_vars['post_type']                    &&
        preg_match( '#^([^/]+)/([^/]+)$#', $query_vars['authors'] )
    ) {
        $works_slug = explode( '/', $query_vars['authors'] )[1];

        // Find "works" post with the above slug.
        $ids = get_posts( array(
            'post_type' => 'works',
            'name'      => $works_slug,
            'fields'    => 'ids',
        ) );

        // If the post exists, then load it.
        if ( ! empty( $ids ) ) {
            $query_vars['post_type'] = 'works';
            $query_vars['name'] = $query_vars['works'] = get_page_uri( $ids[0] );

            unset( $query_vars['authors'] );
        }
    }

    return $query_vars; // ALWAYS RETURN IT
}

Additionally, in the post_type_link filter callback, I would:

  1. Change the 'library/authors/' . $author . '/' . $post->post_name to 'library/authors/' . $author . '/' . ( $leavename ? '%postname%' : $post->post_name ) to ensure the post permalink (the slug part) is editable via the post editing screen (in wp-admin).

  2. Change the $author = basename(get_permalink($author_id)); to $author = $author_id ? get_post_field( 'post_name', $author_id ) : '';.

Update

Sorry, I forgot about preview URLs (e.g. for posts with the status draft or pending) where the URL is not "pretty" and looks like https://example.com/?post_type=works&p=123&preview=true.

So to ensure the URL returned is correct, in your my_website_filter_post_type_link() function, add this at the very top:

if ( $sample || wp_force_plain_post_permalink( $post ) ) {
    return $post_link;
}

I.e. Add it above the line having the switch( $post->post_type ).

Also (to other readers), if your works post type is not hierarchical, replace the get_page_uri( $ids[0] ) with $works_slug.

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.