I have a website with one custom post type as well as a blog.

For the custom post type I would like to have the slug as root, like so


And the blog post I would like to display under /blog, like so


I've managed to "minimize" the custom post type's urls by using these two functions:

add_filter( 'post_type_link', function($post_link, $post, $leavename) {
    if('custom' == $post->post_type && 'publish' == $post->post_status) {
        $post_link = str_replace( '/' . $post->post_type . '/', '/', $post_link);
        return $post_link;

    return $post_link;

}, 10, 3 );


add_action('pre_get_posts', function($query) {
    if ( ! $query->is_main_query() || 3 != count( $query->query ) || ! isset( $query->query['page'] ) ) {

    if ( ! empty( $query->query['name'] ) ) {
        $query->set( 'post_type', array( 'post', 'custom', 'page' ) );

However, I have not managed to move my blog posts to /blog.

I've tried to set the permalink structure to


and the custom post type's rewrite to

'rewrite' => array('slug' => 'custom', 'with_front' => false)

However, this does not produce the desired effect (domain.com/title). Instead, the "custom" slug is now part of the url again. Manually removing it (domain.com/title) produces a 404.

  • Could you explain the conditions you use in pre_get_posts function? Especially the one with 3 != ...? – Krzysiek Dróżdż Jan 15 at 22:19
  • I actually did not write it myself, but found it somewhere. The 3 != part I don't understand myself. – FSH Jan 15 at 22:22
  • OK. I think it's very specific for given case... – Krzysiek Dróżdż Jan 15 at 22:23

moving blog posts to /blog/ is pretty easy and you can use WordPress settings to do that - no coding needed.

enter image description here

The problem you will have though is that after moving posts, your CPT URL structure will conflict with URL structure of pages - so you'll have to modify your pre_get_posts function.

As for your post_type_link you can simplify it a lot (if it is not hierarchical... is it?):

add_filter( 'post_type_link', function($post_link, $post, $leavename) {
    if ('custom' == $post->post_type && 'publish' == $post->post_status) {
        $post_link = site_url('/') . $post->post_name .'/';

    return $post_link;

}, 10, 3 );

And I also wouldn't use pre_get_posts for selecting proper posts. Using parse_request is a lot easier for this task and it allows you to prevent 404 errors a lot easier.

add_action( 'parse_request', function ( $wp ) {
    if ( ! is_admin() ) {
        if ( array_key_exists('error', $wp->query_vars) && '404' == $wp->query_vars['error'] ) {
            $custom_post = get_page_by_path( $wp->request, OBJECT, 'CUSTOM' );

            if ( $custom_post instanceof WP_Post ) {
                unset( $wp->query_vars['error'] );
                $wp->query_vars['page'] = '';
                $wp->query_vars['CUSTOM'] = $wp->query_vars['name'] = $wp->request;
                $wp->query_vars['post_type'] = 'CUSTOM';

    return $wp;
} );
  • Krzysiek, this I have done already. However, together with the code above which is necessary for removing the /custom/ from the custom post type's url, it does not work. I end up with /blog/title and /custom/title which is not what I want (for the custom post type, I only want /title). – FSH Jan 15 at 22:13
  • Sorry, I missed the text below the screenshot. In what way do I need to modify the code in pre_get_posts in order to avoid the conflict? – FSH Jan 15 at 22:15
  • Personally I wouldn't use pre_get_posts for this. parse_request is a little bit earlier and better - it's easier to prevent 404 errors, when you're using it. – Krzysiek Dróżdż Jan 15 at 22:20
  • Ok, that's good to know, thanks! I tried it with parse_request, however I still get the 404. – FSH Jan 15 at 22:25
  • I've updated my answer with parse_request function. Remember to change CUSTOM to your CPT slug. – Krzysiek Dróżdż Jan 15 at 22:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.