I'm seeing something unexpected, although I'm not sure whether it's a feature I can disable or whether it is something introduced by the custom theme I'm using that I need to track down.

If I enter a url for a non-existent page, e.g. mysite.com/london, before throwing a 404 it will search for a post with a title that starts with "london" and show that if it finds it, e.g. mysite.com/events/london-weekend-party.

(I have a number of different CPTs, but this redirect is CPT-agnostic, i.e. it doesn't seem to care which post type contains 'london' in the title. When I register the CPTs the only rewrite rule is for the slug, e.g. 'rewrite' => array('slug'=>'events'.)

I used the plug-in Debug This to look at the rewrite rules, but it hasn't helped because it appears to be showing the rule that matched after the rewrite happened, i.e. not that it matched against mysite.com/london but against the post it found, i.e. events/([^/]+)(/[0-9]+)?/?$.

I only discovered the 'problem' because I was looking at how to intercept 404s before the 404 template is loaded to check the urls against a whitelist so that if I get a match for, say mysite.com/london, I can do something London-specific before a redirect. If anyone has any tips on where to get started with that, I'd be grateful to hear them. My first instinct was to $_GET hooked into wp but I'm not talking about ?name=value style parameters.

1 Answer 1


The magic you're referring to happens in redirect_canonical(). And more specifically, redirect_guess_404_permalink().

You can cut in ahead of redirect_canonical by hooking onto the template_redirect action with a slightly higher priority - in this case, anything lower than 10.

function wpse_145210_redirect_canonical() {
    if ( is_404() && $name = get_query_var( 'name' ) ) {
        // The following is straight from redirect_guess_404_permalink(), do whatever you want instead!

        $where = $wpdb->prepare("post_name LIKE %s", like_escape( $name ) . '%');

        // if any of post_type, year, monthnum, or day are set, use them to refine the query
        if ( get_query_var('post_type') )
            $where .= $wpdb->prepare(" AND post_type = %s", get_query_var('post_type'));
            $where .= " AND post_type IN ('" . implode( "', '", get_post_types( array( 'public' => true ) ) ) . "')";

        if ( get_query_var('year') )
            $where .= $wpdb->prepare(" AND YEAR(post_date) = %d", get_query_var('year'));
        if ( get_query_var('monthnum') )
            $where .= $wpdb->prepare(" AND MONTH(post_date) = %d", get_query_var('monthnum'));
        if ( get_query_var('day') )
            $where .= $wpdb->prepare(" AND DAYOFMONTH(post_date) = %d", get_query_var('day'));

        $post_id = $wpdb->get_var("SELECT ID FROM $wpdb->posts WHERE $where AND post_status = 'publish'");
        if ( ! $post_id )
            return false;

        if ( get_query_var( 'feed' ) )
            $url = get_post_comments_feed_link( $post_id, get_query_var( 'feed' ) );
        elseif ( get_query_var( 'page' ) )
            $url = trailingslashit( get_permalink( $post_id ) ) . user_trailingslashit( get_query_var( 'page' ), 'single_paged' );
            $url = get_permalink( $post_id );

        wp_redirect( $url, 301 );

add_action( 'template_redirect', 'wpse_145210_redirect_canonical', 5 );
  • So it's a feature! Very helpful, now I know where, I can get digging. Thanks.
    – terraling
    May 22, 2014 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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