search ]

Building a Custom Link Structure using the WordPress Rewrite API

One of the important and less known APIs in WordPress is the Rewrite API. This API provides the ability and functionality to create a custom link structure.

In the first part of this post, we will learn how to use the default Query Vars that WordPress provides and see how to add our own custom variables.

In the second part of the post, we will explore the tools WordPress provides us to convert a query structure to a fixed link structure (Permalink) using the Rewrite API interface.

Introduction: URL Structure

URLs are used to send HTTP GET requests over the internet. The GET method sends pairs of key = value within the URL to receive a response from a specific server.

Take, for example, the following website address and pay attention to the last part of this URL:

http://example.com/?p=123

The question mark (?) divides this website address into two parts. The first part is the domain name, and the second part is the Query String.

While WordPress knows that it should fetch the post with ID number 123, it is not semantic or readable for humans, meaning the address does not give us any information about its content.

This situation is not ideal for users and does not contribute to SEO and search engine rankings. Therefore, we have the option to translate non-semantic addresses into semantic addresses using the WordPress Rewrite API.

This is why we call them Permalinks (Permanent Links).

But before we see how to create a link in the form of a query, a link in a fixed structure (Permalink), let’s learn more about those Query Strings and Query Vars.

Part 1: What are Query Vars and Query Strings?

WordPress can be asked to retrieve almost anything from the site’s database. For example, if we want all posts from a specific category, all posts tagged with a specific tag, or even to get a post published on a specific date.

When a user sends a URL, WordPress handles that request according to the template hierarchy rules and displays the results in a single page or an archive page.

Public Query Vars vs. Private Query Vars

Query Vars are the keys that define any query WordPress executes to retrieve information from the database. These variables are divided into two categories:

  • Public Query Vars – Variables in the URL intended for use in a query.
  • Private Query Vars – Variables intended for use only through code.

Let’s take an example URL:

example.com/?author_name=moshe

In this example, the Query Var is author_name, instructing WordPress to provide us with all posts written by the user named moshe. It’s worth noting that multiple variables can be added using the & sign. For example:

example.com/?author_name=moshe&tag=news

In this case, WordPress will provide us with all posts by moshe tagged with news.

In the following example, we see a query for a custom content type (Custom Post Type) named “food,” with a custom taxonomy named food-family, where the term of the taxonomy is greens:

example.com/?post_type=food&food-family=greens

Unlike variables of the Public Query Vars type, Private Query Vars can only be used within a PHP query. This post will not cover them, but you can find both types of Query Vars in the following link.

Now, let’s take a look at all the built-in variables (of the type Public Query Vars) that WordPress provides us:

<?php
$keys = array(
	'error', 
	'm', 
	'p', 
	'post_parent', 
	'subpost', 
	'subpost_id', 
	'attachment', 
	'attachment_id', 
	'name', 
	'static', 
	'pagename', 
	'page_id', 
	'second', 
	'minute', 
	'hour', 
	'day', 
	'monthnum', 
	'year', 
	'w', 
	'category_name', 
	'tag', 
	'cat', 
	'author', 
	'author_name', 
	'feed', 
	'tb', 
	'paged', 
	'comments_popup',
	'preview', 
	's', 
	'sentence', 
	'title', 
	'fields', 
	'menu_order'
);

We can retrieve posts by content type, author, category, tag, taxonomy, year, month, day, and so on. WordPress provides us with variables for almost every type of query, as mentioned. However, what is missing from this list is the ability to build queries based on custom fields.

In fact, WordPress provides us with variables named meta-key and meta-value, but these are not of the Public type suitable for queries in the URL. These are Private variables that can only work within code, meaning through WP_Query and not as part of a URL query. Let’s see how to create new Query Vars…

Creating Custom Query Vars

After adding our own variables, they can coexist within a query just like all the built-in Query Vars of WordPress. So let’s say we have a custom content type for books named book, to which we added a custom field named author_surname indicating the book’s author.

If we want to create a query that finds books based on that author_surname, we need to create a new Query Var. The function below shows how to add new Custom Query Vars to WordPress’s list. In our case, we’ll add a Query Var named book-author. Here’s how:

<?php
/**
 * Register custom query vars
 *
 * @param array $vars The array of available query variables
 *
 * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
 */
function myplugin_register_query_vars( $vars ) {
	$vars[] = 'book-author';
	return $vars;
}
add_filter( 'query_vars', 'myplugin_register_query_vars' );

In the next step, we need to inform WordPress that it should retrieve the value of that specific Query Var when executing the query. This is done using the WordPress function called get_query_var.

To accomplish this, we will create our own function and attach it to a hook named pre_get_posts that occurs just before WordPress “fetches” the posts for us. The function in question looks like this:

<?php
/**
 * Build a custom query
 *
 * @param $query obj The WP_Query instance (passed by reference)
 *
 * @link https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
 */
function myplugin_pre_get_posts( $query ) {
	// check if the user is requesting an admin page 
	// or current query is not the main query
	if ( is_admin() || ! $query->is_main_query() ){
		return;
	}

	// edit the query only when post type is 'book'
	// if it isn't, return
	if ( !is_post_type_archive( 'book' ) ){
		return;
	}

	$meta_query = array();

	// add meta_query elements
	if( !empty( get_query_var( 'book-author' ) ) ){
		$meta_query[] = array( 'key' => 'author_surname', 'value' => get_query_var( 'book-author' ), 'compare' => 'LIKE' );
	}

	if( count( $meta_query ) > 1 ){
		$meta_query['relation'] = 'AND';
	}

	if( count( $meta_query ) > 0 ){
		$query->set( 'meta_query', $meta_query );
	}
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 ); 

In the first part of the function, we check if we are in the admin interface – if so, the function will stop and not return anything. The same applies to a situation where we are not in the main loop of WordPress, to avoid issues with other loops.

Another thing we need to ensure is that we are on the archive page of the content type book, otherwise, we won’t return anything.

After these checks, we create a meta query that will retrieve only posts with the author_surname field (this is the key), and it has a value equal to the value entered for the query var named book-author.

Next, we check if there is more than one item in the array (meaning there is another parameter besides our variable), and if so, we add an AND relation to the array.

The last step is to insert the $meta_query array into the query object, and we do this using the set() method of the WP_Query class. Essentially, we take the value from the URL, then create a regular WP-Query with that value.

Now, after entering the following URL, WordPress will provide us with all posts of the content type book that have a field named author_surname equal to “Moshe”:

http://example.com/?post_type=book&book-author=Moshe

Looks great, but one more thing remains to be done. We want the URL to be in the format of a query instead of a permalink. It should look like this:

http://example.com/book/book-author/Moshe/

For this purpose, we need to tell WordPress to translate (rewrite) the previous URL structure (in query format) into the format of a permanent link, and here comes the use of the Rewrite API of WordPress.

Part 2: Writing Links – Rewrite API

Before we see how to do this using the Rewrite API, let’s take a brief look at the structure of links, the Permalink Structure, in WordPress.

WordPress Permalink Structure

WordPress allows us to choose from several different default permalink structures, but we can also create our own custom permalink structure using various tags that WordPress provides by default. For example, year (%year%), post ID (%post_id%), post author (%author%), and more.

However, beyond that, we can add our own custom tags. Here comes into play the process of rewriting, which is divided into two actions.

Rewrite Tags & Rewrite Rules

The first action is adding a tag (Rewrite tag) for the URL. The second action is adding a rule (Rewrite rule) that links between the tag and the query variable (Query Vars). In our case, we will add a tag for the same Query Var named book-author that we created, and we will do this using the add_rewrite_tag function.

<?php
/**
 * Add rewrite tags
 *
 * @link https://codex.wordpress.org/Rewrite_API/add_rewrite_tag
 */
function myplugin_rewrite_tag() {
	add_rewrite_tag( '%book-author%', '([^&]+)' );
}
add_action('init', 'myplugin_rewrite_tag', 10, 0);

כעת כל שנשאר לנו לעשות זה לומר לוורדפרס לקשר את אותו התג המותאם שהוספנו, ל- Query Var שיצרנו הנקרא book-author. זאת נעשה באמצעות הפונקציה של וורדפרס ליצירת כללי שכתוב הנקראת add_rewrite_rule.

<?php
/**
 * Add rewrite rules
 *
 * @link https://codex.wordpress.org/Rewrite_API/add_rewrite_rule
 */
function myplugin_rewrite_rule() {
	add_rewrite_rule( '^book/book-author/([^/]*)/?', 'index.php?post_type=book&book-author=$matches[1]','top' );
}
add_action('init', 'myplugin_rewrite_rule', 10, 0);

Now, the URL:

http://example.com/?post_type=book&book-author=Moshe/

Will be theoretically identical and provide exactly the same result as the following URL:

http://example.com/book/book-author/Moshe/

WordPress translates (rewrites) this URL to the previous one and then executes the mentioned query.

It is crucial to mention that after adding new Rewrite Tags or Rewrite Rules, we must go to the WordPress admin interface > Settings > Permalinks and save the changes, otherwise the modifications will not take effect.

For your convenience, here is a plugin that I created to perform the actions in this guide, including creating the custom content type. I hope this guide helped you understand more about the power of the WordPress Rewrite API and how it can be beneficial.

<?php
/*
Plugin Name: Custom Query Vars
*/

// Our custom post type function
function create_posttype_book()
{

    register_post_type('book',
        // CPT Options
        array(
            'labels' => array(
                'name' => __('Books'),
                'singular_name' => __('Book')
            ),
            'public' => true,
            'has_archive' => true,
            'rewrite' => array('slug' => 'book'),
            'show_in_rest' => true,
            'supports' => array('title', 'editor', 'author', 'thumbnail', 'custom-fields')

        )
    );
}

// Hooking up our function to theme setup
add_action('init', 'create_posttype_book');


/**
 * Register custom query vars
 *
 * @param array $vars The array of available query variables
 *
 * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
 */
function myplugin_register_query_vars($vars)
{
    $vars[] = 'book-author';
    return $vars;
}

add_filter('query_vars', 'myplugin_register_query_vars');


/**
 * Build a custom query
 *
 * @param $query obj The WP_Query instance (passed by reference)
 *
 * @link https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
 */
function myplugin_pre_get_posts($query)
{
    // check if the user is requesting an admin page
    // or current query is not the main query
    if (is_admin() || !$query->is_main_query()) {
        return;
    }

    // edit the query only when post type is 'food'
    // if it isn't, return
    if (!is_post_type_archive('book')) {
        return;
    }

    $meta_query = array();

    // add meta_query elements
    if (!empty(get_query_var('book-author'))) {
        $meta_query[] = array(
            'key' => 'author_surname',
            'value' => get_query_var('book-author'),
            'compare' => 'LIKE'
        );
    }

    if (count($meta_query) > 1) {
        $meta_query['relation'] = 'AND';
    }

    if (count($meta_query) > 0) {
        $query->set('meta_query', $meta_query);
    }
}

add_action('pre_get_posts', 'myplugin_pre_get_posts', 1);


/**
 * Add rewrite tags
 *
 * @link https://codex.wordpress.org/Rewrite_API/add_rewrite_tag
 */
function myplugin_rewrite_tag()
{
    add_rewrite_tag('%book-author%', '([^&]+)');
}

add_action('init', 'myplugin_rewrite_tag', 10, 0);


/**
 * Add rewrite rules
 *
 * @link https://codex.wordpress.org/Rewrite_API/add_rewrite_rule
 */
function myplugin_rewrite_rule()
{
    add_rewrite_rule('^book/book-author/([^/]*)/?', 'index.php?post_type=book&book-author=$matches[1]', 'top');
}

add_action('init', 'myplugin_rewrite_rule', 10, 0);

Thanks to premium.wpmudev.org for the original article.

0 Comments...

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development