I need to dynamically change which page is being loaded based on the URL. It's a translation system, and a page's slug is different for each language. URLs look like that:




The slugs are stored in a separate table, not in wp_posts or wp_postmeta. The only way I've found to accomplish this is by modifying global WP_Query object in pre_get_posts hook. The problem is that it doesn't work and I don't understand why. WordPress renders index.php instead of single.php as it should for all posts. If I open the page by the original slug or by ID (i.e. ?p=4033), everything works fine.

Below is the code that demonstrates the problem.

add_action('pre_get_posts', 'mt_handle_translation_redirect', 9999);
function mt_handle_translation_redirect ($query) {
    global $wpdb;

    if (!$query->is_main_query()) return;

    // "1178" is a valid post ID
    $query->set('page_id', 1178);

If I open any post page (e.g. 'http://localhost/wordpress/?p=1010'), it will redirect to the page 1178 like expected. If I open a home page, it won't do any redirects.

If we add add_rewrite_rule to the code, it won't make redirects properly anymore, but instead it will try to render the home page:

add_action('init', 'mt_init');
function mt_init() {
    add_rewrite_tag('%tr_language%', '([a-z_-]+)' );
    add_rewrite_tag('%tr_title%', '([\pL_-]+)' );
    add_rewrite_rule('^(fr)/([^/]+)/?', 'index.php?tr_language=$matches[1]&tr_title=$matches[2]', 'top');

It seems that when I call index.php without the specific post ID or a slug, it isn't possible to do a redirect to another page anymore, because it always renders a post list, like on a home page or a category page.

How do I make custom URLs to open another post?

  • You are writing the translation plugin? Apr 14 '17 at 7:25
  • Yes, that is correct. Apr 14 '17 at 7:29
  • I think by the time you get to pre_get_posts, the rule has been parsed and WordPress has already made some decisions about what the query is for. If a rewrite rule doesn't contain query vars that can generate a successful main query on its own, you need to hook query parsing and help things along.
    – Milo
    Apr 14 '17 at 16:11

Huh. I double-checked. On my local server, this bit of code successfully changes the post:

function test_redirect()
        global $wp_query;
        $wp_query->set('page_id', 5); 
add_action('pre_get_posts', 'test_redirect');

You're going to have to do a bit of debugging. On a dev server, var_dump the $wp_query variable a few key places to check that it has what you expect it to.

One alternate solution is to hook into the parse_query action to modify the query earlier.

Another option is hooking into template_redirect later, and then create a brand new wp_query object. This would generally be preferable if you were changing the post to something fundamentally different, instead of just another translation, because it will clear out any other variables that were set by parse_query earlier. It comes with the extra overhead of querying for the post twice. Here's the general idea:

function redirect_translation()
        global $wp_query;
        // Replace wp_query here... query_posts() should work too.
        $wp_query = new WP_Query(array('post_id' => 5));

Doing this in pre_get_posts will cause an infinite loop.

EDIT: Looking over your code again, how is mt_handle_translation_redirect being called? I suspect you may not be updating the global $wp_query object, which is why the real query isn't being modified. Verifying the contents of wp_query a few places (like your template file) would be a good start.

  • None of those seem to work in my case :( The weird thing is that it's getting the correct post, but it renders it on index.php rather than single.php. Here are both of query objects that are displayed during theme header rendering: diffchecker.com/RkHbYAAn (on the left is the query with the original slug that works fine, on the right is the same post retrieved with my URL rewriting rules). Apr 17 '17 at 9:35
  • Also, mt_handle_translation_redirect is called on action hook pre_get_posts, you can see it in mt_translations_init function in code above Apr 17 '17 at 9:40
  • 1
    Your rewrite rules contain no query vars that tell WordPress you are trying to load a single post. When that rule is parsed, the only type of query that has no native query vars set is the main blog query, that's why you see index.php. You either need to hook parse_query and do it yourself, or alter your rule to contain whatever native query var you need to have it parse successfully.
    – Milo
    Apr 17 '17 at 14:55
  • 1
    @VictorMarchuk - this answer has an example of manipulating the request before the is_ conditionals are set. You can also just try setting the correct query vars for a single post query but give it dummy values, so it's identified as a single post query, then you can manipulate that value in a pre_get_post action. Not sure if that'll work, never tried it.
    – Milo
    Apr 24 '17 at 20:33
  • 1
    @VictorMarchuk - you can add your final code as an answer and accept it, it may be helpful to others in the future.
    – Milo
    Apr 25 '17 at 16:55

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.