WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a CPT with slug function (singular) and archive slug functions (plural).

Permalink structure for:

  • singular is /function/[postname]/
  • archive is /functions/[page-x/].

(can poke live at QueryPosts )

This works and looks fine, however I have logged some cases when people manipulate URL directly, like:

  • /functions/[postname] (typing name after archive page)
  • /function/ (erasing function name)

While this is not complicated to correct with some code explicitly, there would be more post types and it seems like data and logic are quite sufficient for generic solution (really I kinda expected WP to handle it, it does quite a lot to correct link issues).

However I have deep WP rewrite traumas and could use some pointers. :)

To sum it up:

How-To catch and redirect URLs that are correct, other than wrongly using archive slug instead of singular and vice versa?

Archive setup is registered like this:

'has_archive' => 'functions',
'rewrite'     => array(
    'feeds'      => false,
    'with_front' => false,
share|improve this question
How did you implement that /functions/ is listing all post types with the prefix function? – toscho Jun 26 '12 at 12:07
@toscho with has_archive argument, added snippet to question. – Rarst Jun 26 '12 at 12:11
not got any answer for you here, but I had a look at the site and it seems pretty handy! Any chance you've got alphabetical organisation of the functions on the roadmap there? – josh Jun 26 '12 at 12:19
@josh very much work in progress, please hit me in email or twitter with things not relevant to the question here. :) – Rarst Jun 26 '12 at 12:21
up vote 4 down vote accepted
  1. Hook into '404_template'. (Example)
  2. Fetch all public custom post types where has_archive is not FALSE.
  3. Find the post type’s has_archive string and see if it is part of the current request’s url.
  4. Try get_page_by_title() with the last part of the requests.
  5. wp_redirect() to the found post’s permalink.
  6. exit;.
share|improve this answer
+1 But I would use the redirect_canonical filter for this. – Stephen Harris Jun 26 '12 at 12:48
@StephenHarris This is called on 'template_redirect' already, which is too early in my opinion. – toscho Jun 26 '12 at 12:53
why? The canonical redirect is there to fix 'wrong' urls. You just need to check is_404(). What's in between lines 7 and 23 that makes it to early? – Stephen Harris Jun 26 '12 at 13:01
It is called on every request. In this case we need mismatched requests only. Always do as little as possible as late as possible. :) – toscho Jun 26 '12 at 13:06
I see your point, but I really don't think it's going to produce much overhead. In general, redirect_canonical is going to be playing nicer with other plug-ins. – Stephen Harris Jun 26 '12 at 13:13

Ok, I was probably overthinking it. In line with suggested algorithm coded out my handler to this so far:

 * Redirect manually edited URLs to proper pages.
 * @param $template
 * @return mixed
static function if_404_template( $template ) {

    $post_types     = get_post_types( array( 'has_archive' => true, ), 'objects' );
    $url_path       = @parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
    $url_path_parts = array_filter( explode( '/', $url_path ) );

    if ( ! empty( $url_path_parts ) ) {
        $last_url_path_part = end( $url_path_parts );

        foreach ( $post_types as $name => $post_type_object ) {
            if ( $name == $last_url_path_part ) {
                wp_safe_redirect( get_post_type_archive_link( $name ), 301 );

            if ( count( $url_path_parts ) > 1 && in_array( $post_type_object->has_archive, $url_path_parts ) ) {
                $post = get_page_by_title( $last_url_path_part, OBJECT, $name );

                if ( ! empty( $post ) ) {
                    wp_safe_redirect( get_permalink( $post ), 301 );

    trigger_error( 'Uncaught 404 at ' . esc_html( $_SERVER['REQUEST_URI'] ) );

    return $template;
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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