— a blog about web development and whatnot by Steve Webster

mod_rewrite is an awesome tool that makes it easy to provide clean URLs or for setting up permanent redirects from legacy URLs to new ones, but rewriting based on an incoming query string is a little more complicated. The trick is to use a RewriteCond to match the query string portion of the URL, and a regular RewriteRule to match the path.

Let's say I've recently updated my site and need to redirect /old-search?query={keywords} to /search/{keywords}. The first step is to set up the RewriteRule for the path:

RewriteRule ^/old-search$       /search/        [NC,L,R=301]

This will indeed redirect the user, and because there's no query string on the substitution string any existing query string will be carried across to the new URL. However, that't not exactly what we want. To capture the value of the query parameter, we need to precede the RewriteRule with a RewriteCond that matches the query string:

RewriteCond %{QUERY_STRING}     ^query=(.*)$    [NC]
RewriteRule ^/old-search$       /search/        [NC,L,R=301]

The last step is to copy the value from the query parameter (which we cunningly wrapped in a capture group in the RewriteCond) into the destination URL, which we can do using the %1 token:

RewriteCond %{QUERY_STRING}     ^query=(.*)$    [NC]
RewriteRule ^/old-search$       /search/%1      [NC,L,R=301]

%N tokens in the RewriteRule substitution string reference values matched by corresponding capture groups in the preceding RewriteCond statement. This is much like the more commonly-used $N tokens that copy values from the pattern portion of the RewriteRule.

Undesirable side-effects

One potentially undesirable side-effect of the above is that the RewriteRule will no longer be matched if the incoming URL has no query string. To remedy this, you can add another RewriteCond before the first matching an empty query string and append the [OR] flag:

RewriteCond %{QUERY_STRING}     ^$              [OR]
RewriteCond %{QUERY_STRING}     ^query=(.*)$    [NC]
RewriteRule ^/old-search$       /search/%1      [NC,L,R=301]

So long as the RewriteCond you want to extract values from is the one immediately preceding your RewriteRule, you can chain as many RewriteCond statements as you like.

Different destinations

Of course, you don't have to use capture groups and dynamically substitute parts of the query string into the destination URL; you can use the same technique to choose between destinations based on the value of a query string parameter.

If you simply wanted to redirect /blog?view=tags to /blog/tags/ and /blog?view=posts to /blog you could do this:

RewriteCond %{QUERY_STRING}     ^view=tags$     [NC]
RewriteRule ^/blog$             /blog/tags/     [NC,L,R=301]

RewriteCond %{QUERY_STRING}     ^view=posts$    [NC]
RewriteRule ^/blog$             /blog/          [NC,L,R=301]

For more information on the vagaries of mod_rewrite, check out the official documentation.