An archives page is a dedicated location on your WordPress site where you can group your content and put it on display for viewers, allowing you to bring together all of your old posts onto one page.
The goal of this tutorial is to show how to build a minimal archives page in plain WordPress without the need of a plugin. The custom archives page we are about to build will group our posts by year and month. It will also give users the ability to find articles by title.
Here’s a sneak peek at what our archives page will look like:
1. Defining a Custom Archives Page Template
First of all, we need to define a custom template for our archives page. To do this, create and upload a file called archives.php
to your current theme directory. This will contain the following code:
<?php /* * Template Name: Archives */ ?> <?php add_action('wp_enqueue_scripts', 'add_archives_dependencies'); function add_archives_dependencies() { $js_path = get_template_directory_uri() . '/assets/js/archives.js'; $css_path = get_template_directory_uri() . '/assets/css/archives.css'; // 'wp-util' is a required dependency wp_enqueue_script( 'archives', $js_path, [ 'wp-util' ]); wp_enqueue_style( 'archives', $css_path); } ?> <?php get_header(); ?> <div class="container"> <h2><?php the_title(); ?></h2> <form> <p class="search-title">Search this site:</p> <p class="search-input"> <input type="text" class="input-box" id="search-input"/> </p> </form> <div class="results" id="results"> </div> </div> <?php get_footer(); ?>
The goal of the custom add_archives_dependencies
function is to import archive.js and archive.css
(which we will see later on) into our archives page. This is achieved by employing the following two functions: wp_enqueue_script
and wp_enqueue_style
.
As we can see in the animated GIF, the search feature does not reload the page. This means that AJAX technologies are used. To make an AJAX call, we need to add an extra dependency to the page. This dependency is wp-util
, which is enqueued along with archives.js
.
2. Creating the Script and Style File
Now, we are going to create the aforementioned files: archives.css
and archives.js
. The former aims to implement the search function, while the latter makes the archives page look stylish.
Here is archives.css
:
html, body, div, h2, h3, h4, p, form { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } body { line-height: 1; } a { margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent; } input { vertical-align: middle; } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: nunito-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; background-color: #ffffff; color: #333; font-size: 18px; line-height: 1.25em; font-weight: 300; } p { line-height: 1.5em; margin-bottom: 1em; } a:link, a:visited, a:hover { color: #333; text-decoration: none; border: none; -webkit-transition: all .3s linear; -moz-transition: all .3s linear; -ms-transition: all .3s linear; -o-transition: all .3s linear; transition: all .3s linear; } a:hover { color: #aaa; } h2, h3, h4 { font-weight: 300; text-align: center; line-height: 1.25; } h2 { font-size: 2em; margin: 2.5em 0 1.25em 0; text-align: center; } h3 { font-size: 1.5em; margin: 2em 0 1em 0; text-align: center; } h4 { font-size: 1em; } .container { overflow: hidden; max-width: 50%; margin: 0 auto; padding: 0 2.5em; } p.search-title { text-align: center; font-weight: 400; } form input.input-box { width: 94%; height: 30px; font-size: 18px; padding: 10px 15px; border: 1px solid #ddd; -webkit-border-radius:4px; -moz-border-radius:4px; border-radius:4px; -webkit-transition: all .3s linear; -moz-transition: all .3s linear; -ms-transition: all .3s linear; -o-transition: all .3s linear; transition: all .3s linear; } form input.input-box:hover { border: 1px solid #aaa; } form input.input-box:focus { outline: none; border: 1px solid #333; } .results { margin-bottom: 5em; } .day p, .month h4 { font-weight: 400; } .month h4 { margin: 2em 0 1em 0; } .day p, .post-title p { margin-bottom: 0; } .day p { padding-right: 60px; width: max-content; } .day-title { display: flex; align-items: flex-start; border-bottom: 1px solid #eee; padding: 10px 0; } @media screen and (max-width: 800px) { .container { max-width: 70em; margin: 0 auto; padding: 0 1.5em; overflow: hidden; } h2, h3, h3, .month h4, p.search-title { text-align: left; } h2 { font-size: 2em; margin: 2em 0 1em 0; } } @media only screen and (max-device-width: 480px) { h2, h3, h3, .month h4, p.search-title { text-align:center; } } @media print { body { font-family: Helvetica, sans-serif; font-size: 14px; background: white; color: black; margin: 10px; width: auto; } .container { display: block; } } @media all and (min-width:320px) and (max-width:667px) { ::-webkit-scrollbar { display: none; } }
And this is archives.js
:
document.addEventListener('DOMContentLoaded', function() { // https://medium.com/@griffinmichl/implementing-debounce-in-javascript-eab51a12311e const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); } clearTimeout(timeout); timeout = setTimeout(later, wait); } } const resultsDiv = document.querySelector('#results'); const searchInput = document.querySelector('#search-input'); const debouncedSearch = debounce(function() { resultsDiv.innerHTML = (`<p style="text-align:center">Loading...</p>`) wp.ajax .post("get_archives_data", { search: searchInput.value }) .done(function(response) { let innerHTML = ``; if (response.length > 0) { let lastYear = 0; let lastMonth = 0; response.forEach(p => { const year = p.year; const month = p.month; if (year !== lastYear) { lastYear = year; lastMonth = 0; innerHTML += `<div class=year><h3>${year}</h3></div>`; } if (month !== lastMonth) { lastMonth = month; innerHTML += `<div class=month><h4>${p.month_name}</h4></div>`; } innerHTML += ` <div class="day-title"> <div class="day"> <p>${p.dayofmonth.padStart(2, '0')}</p> </div> <div class="post-title"> <p id=p${p.id}> <a href="/${p.post_name}"> ${p.encoded_title} </a> </p> </div> </div> `; }); } else { innerHTML = `<p style="text-align:center">Nessun articolo trovato</p>`; } resultsDiv.innerHTML = innerHTML; }); }, 350); searchInput.addEventListener("input", function (e) { e.preventDefault(); debouncedSearch(); }); // loading data the first time the page is rendered debouncedSearch(); }, false);
As you can see based on how these files are referenced in archives.php
, we uploaded these files to assets/js
and assets/css
of our current theme directory respectively. Feel free to place these two files wherever you want, but do not forget to update archives.php accordingly.
As we do not want our search function to suffer from performance issues, we used the debouncing technique. This way, we ensured that the AJAX call is not made too frequently, which is a good way to limit the number of HTTP requests.
As you can see, in archives.js we used the function: wp.ajax.post
. This is one of the wp-util
helpers and allowed us to make the AJAX call to retrieve the required data to populate the archives page.
functions.php
3. Updating
In the wp.ajax.post
function call, we passed “get_archives_data”
as a parameter. That string represents the name of an action that I will show you how to properly register. To do this, append the following lines of code to functions.php
:
<?php // ... add_action( 'wp_ajax_nopriv_get_archives_data', 'get_archives_data' ); add_action( 'wp_ajax_get_archives_data', 'get_archives_data' ); function get_archives_data() { global $wpdb, $wp_locale; $search = isset( $_POST["search"] ) ? $_POST["search"] : ""; $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) as `month`, DAYOFMONTH(post_date) as `dayofmonth`, ID, post_name, post_title FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'publish' AND post_title LIKE '%$search%' ORDER BY post_date DESC"; $results = $wpdb->get_results($query); $ajax_response = []; foreach ( $results as $result ) { $result->month_name = $wp_locale->get_month($result->month); $result->encoded_title = strip_tags(apply_filters('the_title', $result->post_title)); $ajax_response[] = $result; } wp_send_json_success($ajax_response); }
Please note: if your current theme does not have a functions.php file, you have to create one before appending the lines of code onto it.
With these lines, we defined an AJAX endpoint thanks to the add_action
function and wp_ajax
hook. When reached, it returns the data required to populate the archives page (retrieved with a custom query and sent with the wp_send_json_success
function).
4. Creating the Archives Page
Now, it is time to create the custom archives page. Go to your WordPress admin panel and add a new page (Pages » Add New). As you can see in the GIF, we called this page “Archives” but you are free to give it the name that you prefer.
On the right-hand side of your screen, you should see a meta box called “Page Attributes”. Click on the drop-down menu below the “Template” field and choose “Archives” as your page template.
Choose a permalink, save, and publish the page.
Et voila! Your custom archives page is ready to be visited!
Conclusion
The source code of his article can be found in my GitHub repository.
Allowing users to see all your posts in one elegant page is a great feature to add to your WordPress website. This article explored how to achieve this result, as well as how to provide the necessary code to implement a useful search function to help users find the content that interests them quickly.
I hope that you found this article helpful, thanks for reading!