Build a Custom Archives Page in Plain WordPress in Minutes
Tags: archive, blogging, css, programming
Published: 2024-01-19 20:57:31

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:

![Our archives page inspired by Zen Habit Archives page](https://writech.run/wp-content/uploads/2023/09/image.gif)

## 1\. Defining a Custom Archives Page Template

First of all, we need to define a custom [template](https://developer.wordpress.org/themes/template-files-section/page-template-files/) 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
<?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`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/) and [`wp_enqueue_style`](https://developer.wordpress.org/reference/functions/wp_enqueue_style/).

As we can see in the animated GIF, the search feature does not reload the page. This means that [AJAX](https://developer.mozilla.org/en-US/docs/Web/Guide/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`:

```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`:

```javascript
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**](https://medium.com/@griffinmichl/implementing-debounce-in-javascript-eab51a12311e) **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.

## 3\. Updating `functions.php`

In the `wp.ajax.post` function call, we passed `"get_archives_data"` as a parameter. That string represents the name of an [action](https://codex.wordpress.org/Plugin_API/Action_Reference) that I will show you how to properly register. To do this, append the following lines of code to [`functions.php`](https://developer.wordpress.org/themes/basics/theme-functions/#what-is-functions-php)_:_

```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`](https://developer.wordpress.org/reference/functions/add_action/) function and [`wp_ajax`](https://codex.wordpress.org/Plugin_API/Action_Reference/wp_ajax_%28action%29) 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`](https://developer.wordpress.org/reference/functions/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](https://knowthecode.io/labs/wordpress-post-metadata-basics/episode-3) called "Page Attributes". Click on the drop-down menu below the "Template" field and choose "Archives" as your page template.

![The Page Attributes meta box](https://writech.run/wp-content/uploads/2023/09/image-14.png)

Choose a [permalink](https://wordpress.org/support/article/using-permalinks/), 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](https://github.com/Tonel/wp-custom-archives-page).

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!
  
***
_The post "[Build a Custom Archives Page in Plain WordPress in Minutes](https://writech.run/blog/build-a-custom-archives-page-in-plain-wordpress-in-minutes-910258a5884c/)" appeared first on [Writech](https://writech.run)._