I have post type example with 2 records: exam1 and exam2. This post type was created by plugin Types Toolset (www.wp-types.com). So link to exam1 looks like my_site/example/exam1. Also I have 1 GET-parameter: my_site/example/exam1?get=param.
What I need. I need to rewrite URL from my_site/example/exam1?get=param to my_site/param/exam1. I wrote this rule:

RewriteRule ^(exam1|exam2)/([^/]+)/?$ /example/$2/?get=$1 [QSA,L]

This is the right rule. It should work. But it does not work. When I go to the my_site/param/exam1 , it redirects to my_site/example/exam1.
I know that it is redirect_canonical. And I can remove it:

remove_action('template_redirect', 'redirect_canonical'); 

But my RewriteRule still not working - no redirects from my_site/example/exam1?get=param to my_site/param/exam1.
Why? Please help me. I need it very much.

  • Have you thought about flushing the rewrite rule cache? – flomei Feb 26 '16 at 9:16


I think you have some terminology a little mixed up:

  • A URL "rewrite" alters the endpoint of a HTTP request. If http://example.com/param/exam1 really serves content from http://example.com/examples/exam1?get=param, then the former is being rewritten to the latter (though not necessarily visible to the end-user).
  • In the world of URL-rewriting, a "redirect" is a rewrite that (instead of serving the request from the altered endpoint) kicks back a HTTP 300-range status code, telling the client that sent the HTTP request to send a new request to the rewritten URL (thus altering the address visible to the end-user). If a user enters http://example.com/param/exam1 in their address bar, and then the browser is forwarded to http://example.com/examples/exam1?get=param, then the former will have been redirected to the latter.

RewriteRule Analysis

At a glance, I would suspect that your RewriteRule is never matching your URLs; or if it is, not rewriting them as you might expect:

RewriteRule ^(exam1|exam2)/([^/]+)/?$ /example/$2/?get=$1 [QSA,L]
  • Match HTTP request paths:
    • ^(exam1|exam2)/: beginning with exam1/ or exam2/ and capture exam1/exam2 as sub-group $1...
    • ([^/]+): ...followed by 1 or more non-/ characters, which will captured as sub-group $2...
    • /?$: ...possibly followed by a / at the very end.
  • Redirect matched request paths to /example/$2/?get=$1

In practice:

  • A request sent to http://example.com/param/exam1 will not be rewritten as the request path begins with param/ and not exam1/ or exam2/
  • A request to http://example.com/exam1/param/ will be rewritten to be served by the endpoint http://example.com/example/param/?get=exam1
  • A request to http://example.com/exam1/param/test will not be rewritten because the request path does not end at the first path segment after exam1 or exam2


To rewrite the URL you've specified, you need to swap the capture groups in your Regex:

RewriteRule ^([^/]+)/(exam1|exam2)/?$ /example/$2/?get=$1 [QSA,L]

This will match path segments in the desired order and correct your back-references. However, this rule comes with some strange caveats that could cause unexpected behaviors. In practice, requests to:

  • http://example.com/foo/exam1 => http://example.com/example/exam1?get=foo
  • http://example.com/bar/exam1/ => http://example.com/example/exam1?get=bar
  • http://example.com/posts/exam2_announcement => http://example.com/example/exam2?get=posts
  • http://example.com/tests/exam120 => http://example.com/example/exam1?get=tests

It's possible to handle this in WordPress using add_rewrite_rule().

Drop this in functions.php or a plugin. It's longer than a simple ReWriteRule but you'll have a little more flexibility and control on the WordPress side.


if ( ! class_exists( 'ExamRewrite' ) ):

    class ExamRewrite {
        const ENDPOINT_QUERY_PARAM = '__redirect_exam';

        // WordPress hooks
        public function init() {
            add_filter( 'query_vars', array ( $this, 'add_query_vars' ), 0 );
            add_action( 'parse_request', array ( $this, 'sniff_requests' ), 0 );
            add_action( 'init', array ( $this, 'add_endpoint' ), 0 );

        // Add public query vars
        public function add_query_vars( $vars ) {
            $vars[] = static::ENDPOINT_QUERY_PARAM;
            $vars[] = 'get';
            $vars[] = 'exam';
            return $vars;

        // Add Endpoint
        public function add_endpoint() {
            add_rewrite_rule( '^example/(exam1|exam2)', 'index.php?' . static::ENDPOINT_QUERY_PARAM . '=1&exam=$matches[1]', 'top' );
            flush_rewrite_rules( false ); //// <---------- REMOVE THIS WHEN DONE

        // Sniff Requests
        public function sniff_requests( $wp_query ) {
            global $wp;

            if ( isset(
                $wp->query_vars[ static::ENDPOINT_QUERY_PARAM ],
                $wp->query_vars[ 'get' ] ) ) {
                $get     = $wp->query_vars[ 'get' ];
                $exam    = $wp->query_vars[ 'exam' ];
                $new_url = site_url( "/$get/$exam" );
                wp_safe_redirect( $new_url, 301 );

    $wpExamRewrite = new ExamRewrite();

endif; // ExamRewrite

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.