2

I can't work out how to combine a rewrite rule and a custom query var.

I have created a custom 'edition' template, which calls an external API to populate a table with details of a particular book.

https://wwww.example.com/books/editions

I use a custom query var to populate the database:

https://www.example.com/books/editions/?ed=1950

for example, where ed is a unique identifier, normally the year, for an edition.

How do I rewrite this (using a WordPress function), to:

https://www.example.com/books/editions/1950

I have tried many things from the codex, but with no success! So far:

Functions.php:

function mr2018_custom_query_vars_filter($vars) {
    $vars[] .= 'ed';
    return $vars;
    }
    add_filter( 'query_vars', 'mr2018_custom_query_vars_filter' );

MyCustomTemplate.php:

$edition = get_query_var( 'ed' ); 

This works: I can use ?ed=1603 in MyCustomTemplate.php to call the database. Where I am stuck is rewriting this to something readable, and SEO-friendly.

I believe there are functions which will combine these two processes, but I'm not sure which to use in this instance.

3
  • You've gotten half the way there, I don't believe your query var is necessary though, is this meant to be an archive or is it a post? Keep in mind that adding a custom query var doesn't tell WP what to do with it, you need the logic to make it load your custom template etc in template_redirect
    – Tom J Nowell
    Commented Jul 13, 2018 at 15:45
  • My custom template uses the $edition variable to query an external API using Guzzle and populate a table written in the template file. That works. What I am having difficulty with is rewriting the query string as a pretty URL, so /edition?ed=[query] becomes /edition/[query] Commented Jul 13, 2018 at 15:52
  • Also keep in mind you will want to cache those requests at a minimum, remote requests are expensive, you open yourself up to resource exhaustion DOS attacks
    – Tom J Nowell
    Commented Jul 13, 2018 at 16:02

1 Answer 1

0

What you've done is add a query variable, that's great and necessary for what you're trying to do, but there are 2 more steps to take:

  • Making it pretty: Add a rewrite rule to map /books/edition/xxx to index.php?ed=xxx
  • Making it functional: Make it load your custom template so it knows what to do when the user requests index.php?ed=xxx

Making it Functional

Lets assume the user has arrived at example.com/?ed=123. Your filters allow you to use get_query_var('ed') but how to load your template?

First we need to make it do things when the ed query variable is set, WP won't know what to do on its own:

// call render_page() when needed
add_action( 'template_redirect', 'custom_page_template_redirect' );

/**
 * Filter that maps the query variable to a template
 */
function custom_page_template_redirect() {
    global $wp_query;
    if ( ! empty( $wp_query->query_vars['ed'] ) ) {
        // we've found our page, call render_page and exit
        get_template_part( 'ed page template' );
        exit;
    }
}

If we're relying on a main query to pull in content, we'll need to do some extra steps to adjust things:

// don't pull in a full listing of posts in the main query, there's no need
// ( you can comment this out if you're not using a theme template to render content )
add_filter( 'pre_get_posts', 'custom_page_paging_issue' );
/**
 * fix page loops if pulling in a theme template
 */
function custom_page_paging_issue( $query ) {
    if ( !empty( $query->query_vars['custom_page'] ) ) {
        $query->set( 'posts_per_page', 1 );
    }
}

Making it Pretty

So now example.com/?ed=123 should work, but as your question described, we want pretty URLs!

This is where rewrite rules come in. Rewrite rules use add_rewrite_rule, which needs 2 things:

  • A regular expression to match the URL
  • A query parameter based url to map what got matched on to

For example, this rewrite rule allows you to use URLs such as /leaf/22:

add_filter( 'generate_rewrite_rules', 'custom_page_generate_rewrite_rules' );
/**
 * Add our rewrite rules
 */
function custom_page_generate_rewrite_rules( $wp_rewrite ) {
    $custom_page_rules = array(
        '^leaf/([0-9]+)/?' => 'index.php?page_id=$matches[1]'
    );
    $wp_rewrite->rules = $custom_page_rules + $wp_rewrite->rules;
}

Note that whenever you change rewrite rules, you have to flush the permalinks. WP won't regenerate the rules automatically. Some people might try to get around this by calling flush_rewrite_rules on the init hook, but this can cause problems related to the ordering of things, and is very expensive/slow. Simply visiting the permalinks settings page should be enough.

Additionally, it might be tempting to rewrite on to a different URL than index.php, or to rewrite on to a pretty permalink. This will not work. Rewrite rules map on to index.php?key=value type URLs, anything else will not work. For that you need HTAccess or Nginx rules. Rewrite rules aren't redirects either and can't be used to redirect to different URLs

Further Reading

I wrote about this in greater length here, including a helper class to simplify things.

I also wrote a post here describing the full process of getting from a URL to a fully loaded page

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.