In-Depth Look at WordPress Permalinks and URL Rewriting

Updated on June 05, 2017

In today’s post we are going to take an in-depth look at WordPress permalinks and URL rewriting. According to a 1999 post by Jacob Nielsen, a usable website requires:

  • a domain name that is easy to remember and easy to spell
  • short URLs
  • easy-to-type URLs
  • URLs that visualize the site structure
  • URLs that are “hackable” to allow users to move to higher levels of the information architecture by hacking off the end of the URL
  • persistent URLs that don’t change

A URL should never change, as it can be stored and shared in many ways. That’s the reason why we call them permalinks. Furthermore, a URL should be semantic, in the sense of being immediately and intuitively meaningful to non-expert users (more about Semantic URLs on Wikipedia).

In a static web, a URL identifies a resource by its name, as shown in the following example:

http://example.com/path/to/resource/wordpress-permalinks.html

In order to have well structured URLs we just need a well-structured file system and properly named resources.
But the web is dynamic, and we are used to manage websites using database driven CMSs, and URLs will contain a number parameters whose values determine the query to be run against the database. Consider the following example:

http://example.com/?key1=val1&key2=val2

In this URL you’ll notice a separator (the question mark), and a set of key/value pairs (separated by ampersand) which constitute the query string. The URL does not meet usability and accessibility requirements as specified above, and should be converted into a more meaningful and SEO-friendly permalink.
The way these »ugly» URLs are converted into optimized permalinks depends on your web server. If you are an Apache user, you’ll be required to add a set of rewrite directives into the root folder’s .htaccess file. If you are a Nginx user, you’d add a try_files directive into the main configuration file (read more about this topic on Nginx blog).

But don’t worry! Most of the times you won’t be required to configure line by line the web server, because the most popular CMSs allow users to manage the permalink structure easily and securely from the back end. And WordPress is no exception. Admin users will be able to set their custom rewrite rules quickly and easily from the admin panel. Advanced users and developers could get even more thanks to the WordPress Rewrite API, which provides functions and hooks that bring permalink customization to a higher level.

wordpress permalinks

The admin user can easily check his favorite structure from the Settings

URLs determine the query to be run against the database. For this reason, our first step will be a short introduction to WordPress queries.

An overview of WordPress queries

With the specific purpose of building the query, execute it and store results from the database, WordPress provides the WP_Query class. Thanks to this class we don’t need to care about the query because WP_Query will automatically handle the request, build the query and execute it. Then, according to the template hierarchy, WordPress will return the requested resource.

Out the box WordPress admits requests for single posts, pages, post types as well as for a number of archives ordered by category, tag, date, author, and more. Moreover, if default functionalities wouldn’t be enough, developers can build custom queries by creating new instances of the WP_Query class (the query object) or passing specific parameters to an existing instance of the query before its execution.

The query parameters are named query variables and are divided into three groups.

Public query vars: these variables are public in the sense that they are available to be used in public requests (i.e. the URLs).
Thanks to these variables, we can ask for posts by authors:

?author=12
?author_name=mickey

By category or tag:

?cat=4,5,6
?category_name=CMS
?tag=wordpress

By date and time:

?monthnum=201601
?year=2015
?w=13
?day=31

By post or page:

?p=123
?name=hello-world
?page_id=234

And much more.

Private query variables: these variables are not meant to be added to URL query strings. They can be used to affect queries just within a script (a plugin or a theme’s functions.php file).

The following query string would not return the expected result:

?meta_key=city&meta_value=london

meta_key and meta_value are private query variables not to be defined in query strings. They should be passed to an instance of the query object, as I will show you later.

See the full list of public and private query variables in the Codex.

Custom query variables: these user-defined variables can be passed via URL query strings much like public query vars. The main difference between public and custom variables is that WordPress won’t handle custom vars on its own, and we should get their values from a plugin to customize queries.

That being said, let’s get back to permalinks.

Ugly WordPress Permalinks and query vars

Ugly Permalinks show up the query string, i.e. the part of the URL containing a set of query variables (the query string) that will determine the returned resource.

WordPress defaults to Ugly Permalinks structure

WordPress defaults to Ugly Permalinks structure

As an example, consider the following URLs:

http://example.com/?cat=5
http://example.com/?cat=5,7,9

In response to these URLs, WordPress would return the archive of posts belonging to the specified categories.
We are not limited to just one parameter per URL. In the following examples we’re building more complex queries:

?author_name=lucy&category_name=WebDev
?tag=wordpress&m=201606

In the first query string, author_name and category_name will require all posts by the specified author in WebDev category. In the second query string, tag and m will require all posts tagged as wordpress and published on June 2016.
As you can see, we can set more than one query variable and force WordPress to run advanced queries just adding the appropriate key=value pairs to query strings.

Pretty WordPress Permalinks and structure tags

By enabling Pretty Permalinks we set a usable, accessible and SEO-friendly URL structure. Let’s compare the following URLs:

http://example.com/?p=123 
http://example.com/wordpress-permalinks/

In this example, the ugly permalink shows the p variable and its value (the post ID), while the pretty URL shows the post slug.

WordPress provides four Pretty Permalink formats we can choose from in Permalink Settings Screen, as shown in the image below.

default WordPress permalinks

default WordPress permalinks

But we are not limited to default formats, as WordPress allows users to customize the pretty permalink format by setting one or more structure tags.

The custom structure option allows to set a highly customized pretty permalink format

The custom structure option allows to set a highly customized pretty permalink format

These tags are specific keywords wrapped within % character. WordPress provides the following tags:

  • %year%
    The year of post-publication (four digits)
  • %monthnum%
    The month of publication (two digits)
  • %day%
    The day of publication (two digits)
  • %hour%
    The hour of publication (two digits)
  • %minute%
    The minute of publication (two digits)
  • %second%
    The second of publication (two digits)
  • %post_id%
    The post unique ID (integer)
  • %postname%
    The post slug (i.e. the sanitized string representing the title of the post)
  • %category%
    The category slug
  • %author%
    The author slug

Let’s check the Custom structure radio button and add one of the following strings into the text field:

/%author%/%postname%/
/%year%/%postname%/
/%category%/%postname%/

Any of these strings generates a different pretty permalink with specific semantic values, as shown below:

http://example.com/mickey/wordpress-permalinks/
http://example.com/2016/wordpress-permalinks/
http://example.com/CMS/wordpress-permalinks/

In the first example, the resulting URL highlights the authors of the posts. The other two formats point out the year of publication and the post category respectively. It’s up to you to choose the format that suits to you best.

Custom query vars and advanced permalink customization

In addition to public and private query vars, WordPress allows developers and advanced users to define their own custom query vars. Once registered, these variables can be added to query strings, just like public query vars, and their values can be used to affect queries as well.

From now on, I will show you how to build a custom meta query (i.e. a query that retrieves posts by custom field) taking advantage of custom query vars.
In order to accomplish this goal, we will develop a plugin from which to register custom variables, get their values and change the query accordingly.

In the main file of a plugin add the following code:

/**
 * Register custom query vars
 *
 * @param array $vars The array of available query variables
 */
function myplugin_register_query_vars( $vars ) {
	$vars[] = 'city';
	return $vars;
}
add_filter( 'query_vars', 'myplugin_register_query_vars' );

/**
 * Build a custom query
 *
 * @param $query obj The WP_Query instance (passed by reference)
 *
 */
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;
	}

	$city = get_query_var( 'city' );

	// add meta_query elements
	if( !empty( $city ) ){
		$query->set( 'meta_key', 'city' );
		$query->set( 'meta_value', $city );
		$query->set( 'meta_compare', 'LIKE' );
	}
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

Lets’ dissect this code. First we’ve registered a query var named city:

function myplugin_register_query_vars( $vars ) {
	$vars[] = 'city';
	return $vars;
}
add_filter( 'query_vars', 'myplugin_register_query_vars' );

The query_vars filter allows to add, remove or change public query vars before the query execution. The callback function in the example keeps as argument an array of the available variables, adds a new variable and returns the same array.
Following, we’ve used the value of the variable to change the query:

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;
	}

	$city = get_query_var( 'city' );

	// add meta_query elements
	if( !empty( $city ) ){
		$query->set( 'meta_key', 'city' );
		$query->set( 'meta_value', $city );
		$query->set( 'meta_compare', 'LIKE' );
	}
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

The pre_get_posts action hook is triggered after the query is created but before it’s executed. So we can hook a callback function to this action to make our changes to the query before it runs. That’s what happens:

  1. The callback function keeps an instance of the $query object, which is passed by reference, not by value. This means that any changes to the query object affects the original query and not a copy of it. For this reason, we have to be sure of which query is going to be executed (the main query).
  2. Later, we get the city value from the current query string thanks to the get_query_var function.
  3. Finally, if $city is not empty, we can set the meta query elements meta_key, meta_value and meta_compare. These latter are private query vars not available for public requests. Their values can only be set from within the script.

Now activate the plugin, add the city custom field to a number of posts, and check URLs like the following:

http://example.com/?city=London

In response to this request WordPress would return all posts where the city field value is London.

Let’s make them pretty

Our last task is to convert the ugly URL of the example above in a pretty permalink structure. Let’s add the following function to our plugin:

/**
 * Add rewrite tags and rules
 */
function myplugin_rewrite_tag_rule() {
	add_rewrite_tag( '%city%', '([^&]+)' );
	add_rewrite_rule( '^city/([^/]*)/?', 'index.php?city=$matches[1]','top' );
}
add_action('init', 'myplugin_rewrite_tag_rule', 10, 0);

add_rewrite_tag and add_rewrite_rule functions are part of the Rewrite API. add_rewrite_tag makes WordPress aware of the city query var, while add_rewrite_rule specifies a new rewrite rule. Both functions should be hooked to the init action. Thanks to new tag and rule, we can send the following URL:

http://example.com/city/London/

WordPress will return an archive of posts where the city custom field value is London.

Note: anytime a new rewrite rule is added, WordPress permalinks have to be refreshed from Permalinks Screen under Settings admin menu.

Conclusions

WordPress provides a full-fledged system to manage permalinks. Most functionalities are available out of the box, and are accessible from the admin panel. But if we’d require a deeper control over permalinks, a whole set of hooks and functions allows us to add variables to the query strings, and to rewrite URLs into accessible and SEO-friendly WordPress permalinks.

The full code of this post is available on Gist.

This article was written by Carlo Daniele
Carlo is a freelance front-end designer and developer. When he writes articles and tutorials, Carlo mainly deals with web standards, but when he plays with websites his best workmate is WordPress.

Hand-picked related articles

Leave a Reply

Send this to a friend