Building Customized URLs in WordPress: Permalinks, Query Vars and URL Rewriting
The Rewrite API for WordPress is an important feature that you probably don’t read much about, yet you’re no doubt using without even realizing it. The API provides the functionality for creating your own unique links – permalinks – for your website.
In this tutorial, I’ll explain permalinks in-depth – what they are, why they are permanent, their possible structures, and how you can rewrite them in a form that is intelligible for both humans and machines. I’ll also explain some key concepts behind permalinks in WordPress, first looking at how to add variables to non-optimized URLs and how to use these variables and their values to query your database. Later, we’ll explore URL rewriting and how to build the best structure for pretty permalinks.
What we’ll cover in this article:
- What Are Permalinks? What is URL Rewriting?
- URLs in WordPress: Query Vars and Query Strings
- Public and Private Query Vars
- Custom Query Vars
- WP_Query Class
- Custom Field (Meta) Queries
- Listing Posts by Custom Fields
- Pretty Permalinks
- Adding Rewrite Tags
- The Query Monitor plugin
What Are Permalinks? What is URL Rewriting?
URLs are the vehicle used to send HTTP GET requests over the web. More precisely, the GET method submits
key=value pairs within a URL to get a response from a specified resource (read more about this topic at W3Schools).
Take the following URL:
The question mark splits this URL into two parts. The first part is the domain name, the second part is the query string, which is a set of query variables and values determining the resource requested by the user. The query string identifies the resource, but it doesn’t tell us anything about its contents. We can say that it’s not semantically meaningful for humans and machines.
Thanks to the Rewrite API, we can translate non-semantic URLs into their semantic equivalent with an operation of URL rewriting. A rewrite rule would translate the preceding URL into the following structure:
With the category and post title within the URL, this structure describes more precisely the resource content for both humans and search engines, resulting in a usable, accessible and SEO-friendly URL. It can be bookmarked, shared and stored in a number of ways and it should never change in the long term so that the linked resource could be permanently targeted. This is the reason we call them permalinks.
URLs in WordPress: Query Vars and Query Strings
We can ask WordPress to retrieve almost anything from a site’s database. Generally, you query for posts in a specified category, or labeled with a precise tag, or published during a specific period of time. When the user submits a URL, WordPress automatically handles the request and, according to the template hierarchy rules, shows the results in a single page or within an archive.
In the first part of this post, I’ll show you how to make use of built-in query string variables and how to register our own custom variables, we’ll instruct WordPress to get these variables from the URLs and use them to query the database, and then we will output the resulting list of posts into a custom archive page.
In the last part of this post, I’ll show you the tools WordPress provides us to translate these semantically unintelligible query strings into meaningful, accessible, usable and SEO-optimized permalinks. I’ll also give you an overview of default and custom permalink structures provided by WordPress, and finally we’ll build custom pretty permalinks using the Rewrite API.
Public and Private Query Vars
Query vars are the keys that define any SQL query WordPress runs against the database. These variables come in two main categories, depending on the way we can use them. Private query vars can be only set within a script, while public query vars can be sent to WordPress with a query string.
Besides public and private variables, WordPress allows us to register our own custom query vars.
In a URL, public query variables are the keys following the question mark (the query string), and are visible when we’ve not enabled Pretty permalinks on the Settings → Permalinks admin page. The following URL is an example:
author_name is a public query var telling WordPress that the user is looking for all posts by the user
carlodaniele. We can add a number of public query vars to the query string, as we do in the following URL:
Now, WordPress will get all posts by
carlodaniele and tagged as
toolbar. And we can do even more. The next query string is a combination of a custom post type and a taxonomy-name=taxonomy-term pair.
Unlike public variables, private query vars can be only used within a script. For this reason, I won’t explore them in this post (you can read more in the WordPress Codex). Here, I will simply point out that the following URL won’t give us the expected result:
author__in is a private query var, and WordPress won’t show the posts by the specified authors.
Most times, thanks to public query vars, we don’t need to write code to manage a user’s requests, we just need to build the right query strings and WordPress will do the rest.
Now, have a look at the following list of public vars:
We can retrieve posts by type, author, category, tag, taxonomy, year, month, day, and so on. We have a query var for almost any kind of query. What lacks here, though, is the possibility to build queries based on custom fields (we call them meta queries). In fact, WordPress provides the
meta_value query vars, but they belong to the private group of variables, so they’re not available for URL requests, and should be only used in scripts. So, how can we ask for meta queries from URL query strings?
The first step is to register new query vars.
Custom Query Vars
Once registered, these variables can take place in a query string just like any other public query variable. The following function shows us how to add them to the list:
query_vars filter allows us to add, remove or edit existing variables before the query runs. Here we’ve just added two custom variables and, from now on, we can get their values thanks to the
Now the query var values are available to build a custom query.
When the user asks for a specific resource, being it a single page, a search result or a list of posts, WordPress instantiates a new
WP_Query object, whose methods allow us to manipulate the actual SQL query before its execution.
I will assume you’re familiar with the
WP_Query class. If not, before reading over this post take the time to check our In-depth Guide to Conquering WP_Query.
Custom Field (Meta) Queries
We have got a bunch of parameters that allow us to set a custom field query, like the
meta_query argument, which is a multi-dimentional array of single meta queries with the following keys:
- key (string) – a custom field key
- value (string|array) – custom field value
- type (string) – custom field type
- compare (string) – a comparison operator
As an example, we could set the following
'relation' is an optional element setting the logic relation between single queries (defaults to
Outside the WordPress Loop (i.e. in a plugin file), we can pass the array to the
set method of the
$query object as follows:
set method keeps two arguments: the query variable name and its value.
To affect the query, we would change it after the query creation, but before its execution. To accomplish this task, we’ll hook a callback function to the
The following example shows how all this works:
It’s important to note that the
$query object is passed to the function by reference, not by value. This means that any changes to the
$query object affects directly the original query, not just an instance of query. As a consequence, it’s a good practice to be sure that we’re editing just the main query (
! $query->is_main_query()), and the changes are not affecting admin queries (
is_admin()). When needed, we could check other conditions to be sure to change the main query exclusively in specific pages (i.e.
Now, let’s put together all that we’ve been talking about so far in a working example.
Listing Posts by Custom Fields
Say you want to build a book catalogue with WordPress. With this goal, you may register a custom post type named
book adding several custom fields, like
publisher, and so on. And say you want to provide site users with links to archive pages by
Now we know what to do. First, we have to register a query var naming it
Note that the custom query variable has been named
book-author and not
author, which is a reserved term for post authors (view the full list of reserved terms in the Codex). Now WordPress is aware of the query var, and we can get its value from a URL thanks to the
Now consider the following function hooked to
get_query_var() gets the value of
'book-author'. If available, this value is pushed into the
We’ve set a value for the
'relation' element, too, just in case we set more than one meta query.
set method passes the array of parameters to the
$query object, changing the query before its execution.
Now you can send a URL like the following:
And you’ll get all books in your archive where the custom field
author_surname is Rowling.
With a good understanding of
WP_Query object and query vars, we can get anything from the database just sending the proper URLs. It’s time to rewrite these URLs into usable, accessible and SEO-friendly structures.
WordPress provides three permalink structures:
- Ugly permalinks
- Pretty permalinks
- PATHINFO permalinks (index.php appears in the URL)
By default, WordPress uses the ugly permalink structure. (i.e. http://example.com/?post_type=book or http://example.com/?p=123). But we know how important a pretty permalink structure is (i.e. http://example.com/book/harry-potter-and-the-chamber-of-secrets/), so go to the Settings > Permalinks admin page of your install and set your favourite structure.
We can check one of the available options, or set a custom structure where we can provide one or more structure tags. These tags are keywords we can add to permalinks to give them a specific meaning. As an example,
%year% would inform the user about the year of publication of a post.
WordPress provides 10 default structure tags, but we can add any number of custom tags, one for each custom query variable we’ve previously registered.
That being said, our final task is to register a custom structure tag and instruct WordPress on how to use it.
Adding Rewrite Tags
Let’s hook the following function to the
add_rewrite_tag function, registers a new structure tag. The function keeps three arguments: the tag name, a regex to match the tag name, an optional query (not set here).
Now WordPress is aware of the tag. We just need to register the rewrite rule that tells WordPress how to make use of it. Here is the code:
add_rewrite_rule() function will do the magic here:
- The first argument is a regular expression to match against the requested URL;
- The second argument is the URL to fetch when the regex is matched; and
- The last argument is a string whose value can be either
'top'will take precedence over existing rules).
Note that when we register a custom post type, we have to call the
flush_rewrite_rules() on plugin activation, otherwise the new rewrite rules won’t work (read more in the Codex)
Now a URL like the following:
would be rewritten in the following pretty permalink:
Note: Always save Permalink settings when you add or edit rewrite tags and rules, even if you did not change the permalink structure, otherwise tags and rules won’t take effect.
The Query Monitor Plugin
A great developer tool to check the query vars is a free plugin called Query Monitor. This plugin shows matched rewrite rules and query strings, query vars, database queries, hooks and much more. It’s definitely worth a look.
In this post we looked at how to query the WordPress database, passing values from URL query strings using public and custom query variables. We also explored permalinks with a focus on custom structures. Finally, we added new rewrite tags and rules, and built user and SEO-friendly URLs.
I hope you’ve found this tutorial helpful and you can now customize your own URL structure to suit your needs.
Have you dealt with permalinks in WordPress? Share your experiences, examples and questions in the comments below.Tags: