Dougie Hunt

WordPress Sitemap Without Plugins

Another WordPress Tutorial, this time I will take you through creating a custom WordPress sitemap without plugin.

🔥 WordPress 👾 6,324 Views

⭐ Updated 24 August 2022

💥 Published 4 May 2013

Create a WordPress sitemap without plugins

Tired of plugin bloat? Well there is a simple way to create a custom WP sitemap without using any plugins

This is a super simple and quick guide to help you create a WordPress Sitemap without using a plugin. No plugin required at all! Ok, below is the code snippet you’ll need and a quick guide.

Updates for 2022

Based on some feedback I have updated the sitemap page to include:

1. Separate loops for one Custom Post Type and a separate loop for multiple Custom Post Types
2. The ability to exclude certain posts, pages, custom post type posts, woocommerce products
3. Increase readability by increasing spacing and clearer annotations/comments

Updates for 2021

As this is one of the most popular tuts on my blog (thanks for visiting), I have updated this WordPress Sitemap without a plugin for 2022. This includes more annotation, custom post types, and an improved WP Query loop.

1. Custom Post Types: I’ve included CPTs on this sitemap page, so all you need to do is replace the cpt-slug in the WP Query with your CPTs slug
2. Improved WP Query Loop: I’ve added a few extra options on all the WP Query loops below. That includes ordering by alphabetically by title
3. More Annotation: I’ve added some more annotation to make it easier for you to understand and customise the loop below

Why would you want to not use any third-party plugins to create a sitemap?

It’s really simple. All WordPress websites created by the average user should look to maintain a fully functioning site with somewhere around ~10 front-facing plugins. For reasons such as speed, flexibility, security and compatibility. Some plugins which are not secure can leave your website vulnerable, they can also be incompatible with other plugins resulting in your website not loading and producing errors, and the third reason because of speed, some plugins are bulky and overloaded with code.

Oh, and most important of all it is actually super simple to do if you follow these easy instructions below. So, pull up your sleeves you’ve got to get stuck in and learn how to create a WordPress Sitemap without using a plugin.

Create WordPress sitemap without plugin

Before you start – You need to have administrative access to your WordPress site and FTP access. You should also always take a full database and theme file backup before making any changes.

1. Go to your theme folder
2. Create and name a file page-sitemap.php
3. Paste the code below into the page-sitemap.php file
4. Create a new page in the backend of your WordPress install. Select the page template “Sitemap”
5. Now tweak to fit your site

WordPress Sitemap Page Code

Below is the WordPress sitemap file. This supports Posts, Pages, Custom Post Types and WooCommerce Products. If you don’t want or need Custom Post Types or WooCommerce products support then just delete those loop at the bottom (I’ve annotated each section so should be easy to find).

<?php /* Template Name: Sitemap */ get_header(); ?>
<section id="sitemap">
   <div class="sitemap-heading">
      <h1>Sitemap</h1>
   </div>
   <!-- START WordPress Posts Loop -->
   <div class="sitemap-posts">
      <h2>Posts</h2>
      <ul>
         <?php
            $wp_posts = new WP_Query(
                array(
                    'post_type'         => 'post', // slug for posts
                    'posts_per_page'    => -1, // -1 shows all posts
                    'post_status'       => 'publish', // only shows published posts
                    'post__not_in'      => array( 1, 2, 3 ) // Enter post ids here to exclude posts
                    'orderby'           => 'title', // orders by post title
                    'order'             => 'ASC' // orders post title alphabetically
                )
            );
            ?>
         <?php while ( $wp_posts->have_posts() ) : $wp_posts->the_post(); ?>
         <li>
            <a href="<?php echo get_permalink(get_the_ID()); ?>" rel="dofollow" title="<?php the_title(); ?>">
            <?php the_title(); ?>
            </a>
         </li>
         <?php endwhile; wp_reset_query(); ?>
      </ul>
   </div>
   <!-- END WordPress Posts Loop -->
   <!-- START WordPress Pages Loop -->
   <div class="sitemap-pages">
      <h2>Pages</h2>
      <ul>
         <?php
            $wp_pages = new WP_Query(
                array(
                    'post_type'         => 'page', // slug for pages
                    'posts_per_page'    => -1, // -1 shows all pages
                    'post_status'       => 'publish', // only shows published pages
                    'post__not_in'      => array( 1, 2, 3 ) // Enter post ids here to exclude pages
                    'orderby'           => 'title', // orders by page title
                    'order'             => 'ASC' // orders page title alphabetically
                )
            );
            ?>
         <?php while ( $wp_pages->have_posts() ) : $wp_pages->the_post(); ?>
         <li>
            <a href="<?php echo get_permalink(get_the_ID()); ?>" rel="dofollow" title="<?php the_title(); ?>">
            <?php the_title(); ?>
            </a>
         </li>
         <?php endwhile; wp_reset_query(); ?>
      </ul>
   </div>
   <!-- END WordPress Pages Loop -->
   <!-- START WordPress Custom Post Type Loop - Delete this section if you are not using Custom Post Types -->
   <div class="sitemap-custom-post-types">
      <h2>Custom Post Type</h2>
      <ul>
         <?php
            $wp_cpt = new WP_Query(
                array(
                    'post_type'         => 'cpt-slug', // Change this to the slug of your Custom Post Type (cpt)
                    'posts_per_page'    => -1, // -1 shows all cpt posts
                    'post_status'       => 'publish', // only shows published cpt pages
                    'post__not_in'      => array( 1, 2, 3 ) // Enter post ids here to exclude custom posts
                    'orderby'           => 'title', // orders by cpt post title
                    'order'             => 'ASC' // orders cpt posts title alphabetically
                )
            );
            ?>
         <?php while ( $wp_cpt->have_posts() ) : $wp_cpt->the_post(); ?>
         <li>
            <a href="<?php echo get_permalink(get_the_ID()); ?>" rel="dofollow" title="<?php the_title(); ?>">
            <?php the_title(); ?>
            </a>
         </li>
         <?php endwhile; wp_reset_query(); ?>
      </ul>
   </div>
   <!-- END WordPress Custom Post Type Loop - Delete this section if you are not using Custom Post Types -->
   <!-- START WordPress Multiple Custom Post Type Loop - Delete this section if you are not using Custom Post Types -->
   <div class="sitemap-custom-post-types">
      <h2>Multiple Custom Post Types</h2>
      <ul>
         <?php
            $wp_multiple_cpts = new WP_Query(
                array(
                    'post_type'         => array('cpt-slug-1', 'cpt-slug-2', 'cpt-slug-3'), // Change this to the slug of your Custom Post Type (cpt)
                    'posts_per_page'    => -1, // -1 shows all cpt posts
                    'post_status'       => 'publish', // only shows published cpt pages
                    'post__not_in'      => array( 1, 2, 3 ) // Enter post ids here to exclude custom posts
                    'orderby'           => 'title', // orders by cpt post title
                    'order'             => 'ASC' // orders cpt posts title alphabetically
                )
            );
            ?>
         <?php while ( $wp_multiple_cpts->have_posts() ) : $wp_multiple_cpts->the_post(); ?>
         <li>
            <a href="<?php echo get_permalink(get_the_ID()); ?>" rel="dofollow" title="<?php the_title(); ?>">
            <?php the_title(); ?>
            </a>
         </li>
         <?php endwhile; wp_reset_query(); ?>
      </ul>
   </div>
   <!-- END WordPress Multiple Custom Post Type Loop - Delete this section if you are not using Custom Post Types -->
   <!-- START WooCommerce Products Loop - Delete this section if you are not using WooCommerce -->
   <div class="sitemap-woocommerce-products">
      <h2>Products</h2>
      <ul>
         <?php
            $woo_products = new WP_Query(
                array(
                    'post_type'         => 'product', // slug of woocommerce products
                    'posts_per_page'    => -1, // -1 shows all products
                    'post_status'       => 'publish', // only shows published products
                    'post__not_in'      => array( 1, 2, 3 ) // Enter post ids here to exclude products
                    'orderby'           => 'title', // orders by product title
                    'order'             => 'ASC' // orders product title alphabetically
                )
            );
            ?>
         <?php while ( $woo_products->have_posts() ) : $woo_products->the_post(); ?>
         <li>
            <a href="<?php echo get_permalink(get_the_ID()); ?>" rel="dofollow" title="<?php the_title(); ?>">
            <?php the_title(); ?>
            </a>
         </li>
         <?php endwhile; wp_reset_query(); ?>
      </ul>
   </div>
   <!-- END WooCommerce Products Loop - Delete this section if you are not using WooCommerce -->
</section>
<?php get_footer(); ?>

WordPress Tutorial: Create WordPress Sitemap Page Without Plugin

Comments

Take part in the discussion

Discussion about WordPress Tutorial: Create WordPress Sitemap Page Without Plugin article, if you have any questions, comments or thoughts then get leave a reply.

  1. Dougie,

    This was so clean and clear – and therefore, easy. Oh that all tutorials could be done this way.

    Well done.

  2. Hi Dougie. Excellent tutorial.

    I just implemented it and, it worked perfect.

    But…

    Could you tell me what code I have to add to exclude certain pages?

    From already thank you very much.

    Greetings.

    • Yes, I have added that above as many people have asked about exclusions in the WP Query Loop.

      In the array you use:

      ‘post__not_in’ => array( X, Y, Z ),

      where X Y Z are the Page IDs of the exclusions.

      $wp_posts = new WP_Query(
      array(
      ‘post_type’ => ‘post’, // slug for posts
      ‘posts_per_page’ => -1, // -1 shows all posts
      ‘post_status’ => ‘publish’, // only shows published posts
      ‘post__not_in’ => array( 1, 2, 3 ) // Enter post ids here to exclude posts
      ‘orderby’ => ‘title’, // orders by post title
      ‘order’ => ‘ASC’ // orders post title alphabetically
      )
      );

    • Hi Prashanth,

      Good question, I think I need to answer that in a new article and provide a snippet. Keep an eye on my blog and I’ll get that up as soon as possible.

  3. Good job! I should have guessed it was as straightforward as this but thanks for confirming. Makes the web great again!!
    Cheers

  4. Hi Dougie. First of all, a thousand apologies for my English (Strictly speaking, I’m using Google translate, heh:)

    That said, excellent tutorial!
    So much so that, despite my difficulties with the language, guess what, exactly, I did it!

    But I have a drawback: There are certain pages that I would NOT want to appear.

    Ergo, could you tell me how I solve it?

    From already thank you very much.

    Greetings!

    • Hi Francisco,

      I have added exclusions as many people have asked about it in the WP Query Loop.

      In the array you use:

      ‘post__not_in’ => array( X, Y, Z ),

      where X Y Z are the Page IDs of the exclusions.

      $wp_posts = new WP_Query(
      array(
      ‘post_type’ => ‘post’, // slug for posts
      ‘posts_per_page’ => -1, // -1 shows all posts
      ‘post_status’ => ‘publish’, // only shows published posts
      ‘post__not_in’ => array( 1, 2, 3 ) // Enter post ids here to exclude posts
      ‘orderby’ => ‘title’, // orders by post title
      ‘order’ => ‘ASC’ // orders post title alphabetically
      )
      );

  5. Hello,

    Thank you for this article.
    For some reasons, my CPT pages would not appear into my Yoast sitemap.
    So I used your code.

    I must say that the get_permalink() function was not working either (would return ‘?p=123’ instead of ‘/my-cpt-page-title/’, so I had to tweak it a little though.

    • Ooo check your Permalinks settings are on ‘Post Name’

      Go to WP Admin > Settings > Permalinks > Common Settings > Select ‘Post Name’

      Here: example.com/wp-admin/options-permalink.php

    • Hi Nemo,

      I’ve now added this to the sitemap above so you can see full info up there.

      In the array you use:

      ‘post__not_in’ => array( X, Y, Z ),

      where X Y Z are the Page IDs of the exclusions.

      $wp_posts = new WP_Query(
      array(
      ‘post_type’ => ‘post’, // slug for posts
      ‘posts_per_page’ => -1, // -1 shows all posts
      ‘post_status’ => ‘publish’, // only shows published posts
      ‘post__not_in’ => array( 1, 2, 3 ) // Enter post ids here to exclude posts
      ‘orderby’ => ‘title’, // orders by post title
      ‘order’ => ‘ASC’ // orders post title alphabetically
      )
      );

WordPress Powered By