Don't miss out on any SEO opportunity, no matter how small.

An HTML sitemap gives your busy readers a navigation of your website, but more importantly, it is good for SEO. And sometimes you just don’t need another unifunctional plugin. So, I’m here to show you how to create an HTML sitemap for your website, without using any plugin.

Note: This tutorial is gear towards coding enthusiasts who want to sharpen your WordPress development skills. If you don’t want to mess with codes, check out this awesome plugin that lets you create an HTML sitemap in minutes.

What does a sitemap page look like?

An HTML sitemap varies depending on the information structure of a web site. Some web masters like to take it up a notch with CSS styling.

However, at its simplest form, it should contain the categories that your web site have, and articles filed under them.

How does an HTML sitemap work?

In order to create an HTML sitemap, we will create a page template that interacts with the WordPress database and retrieve categories and relevant posts.

First, the code will go through each page that your website has, and list them. Of course, you can tell WordPress to exclude certain pages.

Next, it will output the posts. A post in WordPress is dynamic. One post can have multiple categories, and one category can contain many posts. That begs several questions:

  • How do you display a post with multiple categories?

As you can see, my post on search optimization for your most important web pages is filed under both Marketing and Business categories. It would look awkward on the sitemap if I display the post twice, each under a different category.

Thus, my solution is to display the post under whatever category that is output first, which is Business in this case, and don’t display it the second time under Marketing.

  • How do you handle a category with nothing to display?

Let’s go back to the example above. The post about SEO optimization for web pages only appears under the Business category. Unfortunately, Marketing happens to contain only one post, there will be nothing to display under it.

This is a scenario that I didn’t expect. There are three lonely category on my HTML sitemap: life management, Marketing, and WordPress development.

The Kon Mari fan in me can’t stand the sight of a category with nothing to show. So my solution is to remove them altogether.

What you should know beforehand

This coding tutorial requires modifying your current theme. Thus, before you create an HTML sitemap, make sure that you are using a child theme for your website, not your parent theme.

This serves as a backup plan so you can always revert to your parent theme in any case. Please heck out my beginner-friendly tutorial on how to create a child theme for your website.

How to create an HTML sitemap

Make a page template for your sitemap

First of all, access your WordPress file using an FTP client. In your current theme folder, look for the page.php file and duplicate it. Rename it as sitemap.php

Then, move this page template to the folder that stores your page templates. The folder is usually called page-templates. Alternatively, you can always tell a file is a page template if it contains a page prefix in its name.

Now, right click on the sitemap.php file and open it on a code editor on your local computer.

Make HTML sitemap—page template

This is what our sitemap.php file looks like currently. It is exactly the same as the page.php file, only different in the file name, so we need to move on to the next step.

At the beginning of your sitemap.php file, insert this comment section to tell other developers that this is a sitemap template:

<?php
/*
Template Name: Sitemap
*/
?>

Output list of pages

To output pages and posts, insert the following code:

<h2 id="pages">Pages</h2>
<ul>
<?php
// Add pages you'd like to exclude in the exclude here
wp_list_pages(
  array(
    'exclude' => '',
    'title_li' => '',
  )
);
?>
</ul>

<h2 id="posts">Posts</h2>

<?php
$all_posts = new WP_Query(array('posts_per_page'=>-1));
?>
<ul>

Output categories and hyperlinks to post

$cats = get_categories();
foreach ($cats as $cat):
  echo "<li><h3>".$cat->cat_name."</h3>";
  echo "<ul>";
  while($all_posts->have_posts()):
    $all_posts->the_post();
    $category = get_the_category();
    // Only display a post link once, even if it's in multiple categories
    if ($category[0]->term_ID == $cat->term_ID):
      echo '<li><a href="'.get_permalink().'">'.get_the_title().'</a></li>';
    endif;
  endwhile;
  echo "</ul>";
  wp_reset_postdata();
  
  echo "</li>";
endforeach;

What does this code block do? First, function get_categories() generates all the categories of your website. Then the foreach statement goes through each category and output the name.

After that, we use a WordPress loop to move through each post in the queue and generate categories it belongs to.

Now, the if ($category[0]->term_ID == $cat->termt_ID) checks if we are currently in the first category of the current post. If yes, we will display the post title and its hyperlink. If no, we move on.

Storing “empty” categories

As I’ve mentioned before, some categories are not really empty. They contain one post, but that post have been displayed under earlier categories, so they are left with nothing.

Here comes the rescue: post the below code blocks just before the foreach statement.

$cats_to_skip = array();
while($all_posts->have_posts()):
  $all_posts->the_post();
  $post_cats = get_the_category();
    for ($i = 1; $i < count($post_cats); $i++) {
        if ($post_cats[$i]->count === 1):
        array_push($cats_to_skip, $post_cats[$i]->term_ID );
        endif;
    }
endwhile;

First, we create an empty array to store those orphaned categories. Then we go through each post in the database and generate its categories.

The for loop $i = 1; $i < count($post_cats); $i++ checks if the category is not the first category for the post. The next if statement $post_cats[$i]->count === 1 checks if that category only has one post.

If both conditions are met, we will add that category ID to the array we created earlier.

Change the category output

At this time, nothing has changed yet. We need to go back to the foreach loop to make sure that we don’t output categories belonging to the unfortunate $cats_to_skip array.

Hurry up, this code should go inside the foreach loop, before the echo statement for the category name.

if (($cat->term_id !== 1) && !in_array($cat->term_id, $cats_to_skip)):
    echo "<li><h3>".$cat->cat_name."</h3>";
endif;

Display the sitemap on the front end

Now the the most convoluted part of our coding tutorial have passed, but we are not there yet. To finish, go to the administrator and create a new page

Simple give your page a descriptive name, set the page template as sitemap and leave the content section intact.

Wrap up

The whole codes for your sitemap.php file should look like this:

<?php
/*
Template Name: Sitemap
*/
?>
<h2 id="pages">Pages</h2>
<ul>
<?php
// Add pages you'd like to exclude in the exclude here
wp_list_pages(
  array(
    'exclude' => '',
    'title_li' => '',
  )
);
?>
</ul>
<h2 id="posts">Posts</h2>
<?php
$all_posts = new WP_Query(array('posts_per_page'=>-1));
?>

<ul>
<?php
// Add categories you'd like to exclude in the exclude here
$cats = get_categories();
$cats_to_skip = array();

while($all_posts->have_posts()):
  $all_posts->the_post();
   
  $post_cats = get_the_category();
  
  for ($i = 1; $i < count($post_cats); $i++) {
    if ($post_cats[$i]->count === 1):         
      array_push($cats_to_skip, $post_cats[$i]->term_id );
    endif;
      
  }
endwhile;

$cats_to_display = array();
foreach ($cats as $cat):
  if (($cat->term_id !== 1) && !in_array($cat->term_id, $cats_to_skip)):
    echo "<li><h3>".$cat->cat_name."</h3>";
  endif;
  echo "<ul>";

  while($all_posts->have_posts()):
    $all_posts->the_post();
    $category = get_the_category();
    
    // Only display a post link once, even if it's in multiple categories
    if ($category[0]->term_id == $cat->term_id):
      echo '<li><a href="'.get_permalink().'">'.get_the_title().'</a></li>';
    endif;
  endwhile;

  echo "</ul>";
  wp_reset_postdata();
  echo "</li>";
endforeach;
?>
</ul>

And my HTML sitemap looks like this:

Okay, I admit that the part on setting up conditions to filter out “empty” categories took me a full day. I had expected that it would be hard to create an HTML sitemap without plugins, but I couldn’t foresee the multiple posts-multiple categories part.

My brain almost melted from frustration, but on a positive note, I’ve learned more about PHP than any course I’ve taken. Real life scenarios are certainly more interesting than some fictitious projects and inform me about things that I don’t know that I don’t know.

Lessons learned: In an ideal world, don’t file your posts under multiple categories.

Now, it’s your turn. Do you have another approach to creating an HTML sitemap? Is there any part in my codes which could have been shortened, for performance’s sake? I’d love to hear your thoughts. Don’t be shy.

Stay inspired. Subscribe to my infrequent newsletters.(You won't regret it).

Leave a Reply

Your email address will not be published. Required fields are marked *