WordPress: Add category to permalink and redirect old permalinks

November 24, 2016

Occasionally you run across a situation where a website permalink structure needs to be changed from what it has been to a new structure. Most sites that have been public will have links to them from other websites that need to properly redirect to the new permalink structure. Removing an element from the permalink structure and redirecting old links is trivial, however adding an additional element to the url structure can be difficult. For example:

A fairly typical structure is: /%year%/%month%/%day%/%postname%/

Lets use a simple example of adding the category to the permalink structure: /%category%/%year%/%month%/%day%/%postname%/

To provide a more useful example:

Original: https://ben.lobaugh.net/2016/05/15/cruising-the-san-juans
With category: https://ben.lobaugh.net/sailing/2016/05/15/cruising-the-san-juans

The original url is going to display the 404 page. I am going to provide an example that you can use in your own site to capture the url causing the 404 and attempt to locate the new post permalink. If none can be found it will fallback to the 404 page. As long as there is something in the original url you can use to locate a post (like the  %postname%) this method will work with minor tweaks.

add_action( 'template_redirect', 'maybe_redirect_404_old_permalink' );
/**
 * Attempts to forward old permalinks to the new permalink structure
 *
 * @author Ben Lobaugh
 */
function maybe_redirect_404_old_permalink() {
    // Only run this function if we are on a 404
    if( ! is_404() ) {
        return;
    }
 
    // "trick" to get the full URL
    $url = add_query_arg( '', '' );

    /*
     * Pull the URL path apart to find a slug (post_name)
     * The final segment should be the slug
     */
    $parts = explode( '/', $url );
    $parts = array_filter( $parts );
    $size = count( $parts );
    $maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based

    // Attempt to locate corresponding post in the database
    $args = array(
        'name'        => $maybe_slug,
        'post_type'   => 'post',
        'post_status' => 'publish',
        'numberposts' => 1,
    );

    $posts = get_posts( $args );

    // Identify a found post
    if( $posts && ! empty( $posts[0]->ID ) ) {
        $post_id = $posts[0]->ID;

        $post_url = get_permalink( $post_id );

        // Attempt to forward to the new post permalink
        if( $post_url ) {
            wp_safe_redirect( $post_url, 301 ); // Permanent redirect
        }
    }

    /*
     * If we made it down here then we could not find a matching post in
     * the database. No biggie, simply do nothing and display the 404 page
     * as normal :)
     */
}

12 thoughts on “WordPress: Add category to permalink and redirect old permalinks

  1. Mischa (April 21, 2017)

    This is exactly the use case I was searchin for. However after thinking this through I came to the conclussion to not make this redirects. I simply keep my old permalink structure, and won’t add the category. Since this approach will put performance at risk.

  2. Ahmad (December 5, 2017)

    EXACTLY WHAT I NEEDED! Thanks a lot for this amazing piece of code. I think this should be an essential to have in wordpress. I was worried that changing urls might hurt my sites SEO but this saved me!

  3. Bob (October 25, 2018)

    Spot on thank you 🙂

  4. Steve (February 12, 2019)

    This is awesome – works like a charm. Thank you!

  5. kevin (September 4, 2019)

    Sorry but not following completely, where do I add this code?

  6. Carolyn Wilson (February 26, 2020)

    Excellent. Thank you!

    This system for URL’s is also helpful for setting up conditional calls to action within the blog. For example, “Category1”-related offer appears only on url’s containing “Category1”

    🙂

  7. JH (March 18, 2020)

    This has been working fine for us until we enabled w3 total cache plugin. Any idea what I can look for in the w3 total cache plugin to see what this stopped working?

    1. JH (May 4, 2020)

      Doesn’t work with W3 Total Cache.

  8. Mohammed (April 23, 2020)

    Hi,
    Thanks for the code.. I think it does not work really with get_posts so I changed it to use WP_Query

    add_action( ‘template_redirect’, ‘maybe_redirect_404_old_permalink’ );
    /**
    * Attempts to forward old permalinks to the new permalink structure
    *
    * @author Ben Lobaugh
    */
    function maybe_redirect_404_old_permalink() {
    // Only run this function if we are on a 404
    if( ! is_404() ) {
    return;
    }

    // “trick” to get the full URL
    $url = add_query_arg( ”, ” );

    /*
    * Pull the URL path apart to find a slug (post_name)
    * The final segment should be the slug
    */
    $parts = explode( ‘/’, $url );
    $parts = array_filter( $parts );
    $size = count( $parts );
    $maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based

    // Attempt to locate corresponding post in the database
    $args = array(
    ‘name’ => $maybe_slug,
    ‘post_type’ => ‘post’,
    ‘post_status’ => ‘publish’,
    ‘numberposts’ => 1,
    );

    $loop = new WP_Query( $args );

    while ( $loop->have_posts() ) : $loop->the_post();
    $post_url = get_permalink();
    if( $post_url ) {
    wp_safe_redirect( $post_url, 301 ); // Permanent redirect
    exit;
    }
    endwhile;

    wp_reset_postdata();
    }

    Regards

  9. Neil Lunghofer (July 16, 2020)

    Oh my goodness! Impressive article dude! Thank you so much, However I am experiencing troubles with your RSS. I don’t understand the reason why I cannot join it. Is there anybody getting the same RSS issues? Anyone who knows the answer will you kindly respond? Thanx!!

  10. Konstantin (August 24, 2020)

    Hi! Great function, many thanks!
    I also upgraded it to work with GET params like ?ab=cd (otherwise it doesn’t work)

    function maybe_redirect_404_old_permalink() {
    // Only run this function if we are on a 404
    if( ! is_404() ) {
    return;
    }

    // “trick” to get the full URL
    $url = add_query_arg( ”, ” );
    $regex = “#\?(.*?)$#smi”;
    $params = “”;
    if(preg_match($regex, $url, $match)) {
    $params = $match[1];
    $url = preg_replace($regex, ”, $url);
    }
    /*
    * Pull the URL path apart to find a slug (post_name)
    * The final segment should be the slug
    */
    $parts = explode( ‘/’, $url );
    $parts = array_filter( $parts );
    $size = count( $parts );
    $maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based

    // Attempt to locate corresponding post in the database
    $args = array(
    ‘name’ => $maybe_slug,
    ‘post_type’ => ‘post’,
    ‘post_status’ => ‘publish’,
    ‘numberposts’ => 1,
    );

    $posts = get_posts( $args );

    // Identify a found post
    if( $posts && ! empty( $posts[0]->ID ) ) {
    $post_id = $posts[0]->ID;

    $post_url = get_permalink( $post_id );

    // Attempt to forward to the new post permalink
    if( $post_url ) {
    if(!empty($params)) {
    $post_url .= “?” . $params;
    }
    wp_safe_redirect( $post_url, 301 ); // Permanent redirect
    }
    }

    /*
    * If we made it down here then we could not find a matching post in
    * the database. No biggie, simply do nothing and display the 404 page
    * as normal
    */
    }

  11. Soren (January 19, 2021)

    Hello,

    Where do I add this code please?

Leave a Reply to Steve Cancel reply

Your email address will not be published. Required fields are marked *