An HTML sitemap is a simple way to display all your website’s content in one place. It helps visitors and search engines quickly navigate your site’s structure. In this tutorial, we’ll show you how to create a WordPress shortcode that generates an HTML sitemap displaying Pages, Posts, and Custom Post Types (CPTs) – with an option to exclude certain post types you don’t want to display.
By the end, you’ll have a clean, dynamic sitemap you can insert anywhere using [html_sitemap exclude=”product,portfolio”].
Here’s an example:
Pages
- Home
- About Us
- Contact
Blog Posts
- How to Build a WordPress Site
- Top 10 SEO Tips
Products
- Product A
- Product B
Add this code to your theme’s functions.php
file or in a custom plugin:
// [html_sitemap] shortcode with dynamic exclude option
function generate_html_sitemap($atts) {
// Parse shortcode attributes
$atts = shortcode_atts(array(
'exclude' => '', // Default: no exclusions
), $atts, 'html_sitemap');
// Convert exclude attribute into array
$excluded_post_types = array();
if (!empty($atts['exclude'])) {
$excluded_post_types = array_map('trim', explode(',', $atts['exclude']));
}
ob_start();
echo '<div class="html-sitemap">';
// List Pages (exclude if "page" is in exclusions)
if (!in_array('page', $excluded_post_types)) {
echo '<h2>Pages</h2>';
echo '<ul>';
wp_list_pages(array(
'title_li' => '',
'sort_column' => 'menu_order, post_title',
));
echo '</ul>';
}
// List Posts (exclude if "post" is in exclusions)
if (!in_array('post', $excluded_post_types)) {
echo '<h2>Blog Posts</h2>';
$posts = get_posts(array(
'numberposts' => -1,
'post_type' => 'post',
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
));
if (!empty($posts)) {
echo '<ul>';
foreach ($posts as $post) {
echo '<li><a href="' . get_permalink($post->ID) . '">' . esc_html($post->post_title) . '</a></li>';
}
echo '</ul>';
}
}
// List Custom Post Types
$args = array(
'public' => true,
);
$custom_post_types = get_post_types($args, 'objects');
if (!empty($custom_post_types)) {
foreach ($custom_post_types as $post_type) {
if (in_array($post_type->name, $excluded_post_types)) {
continue; // Skip excluded post types
}
// Skip 'post' and 'page' since they’re handled above
if ($post_type->name === 'post' || $post_type->name === 'page') {
continue;
}
echo '<h2>' . esc_html($post_type->labels->name) . '</h2>';
$cpt_posts = get_posts(array(
'numberposts' => -1,
'post_type' => $post_type->name,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
));
if (!empty($cpt_posts)) {
echo '<ul>';
foreach ($cpt_posts as $cpt_post) {
echo '<li><a href="' . get_permalink($cpt_post->ID) . '">' . esc_html($cpt_post->post_title) . '</a></li>';
}
echo '</ul>';
}
}
}
echo '</div>';
return ob_get_clean();
}
add_shortcode('html_sitemap', 'generate_html_sitemap');
Optional: Add Some Styling
Add this to Appearance → Customize → Additional CSS in your WordPress admin.
.html-sitemap ul {
list-style: disc;
margin-left: 20px;
}
.html-sitemap h2 {
margin-top: 20px;
font-size: 1.5em;
color: #333;
}
Usage Examples
Default (show everything): [html_sitemap]
Exclude posts and products: [html_sitemap exclude=”post,product”]
Exclude pages only: [html_sitemap exclude=”page”]
Exclude multiple custom post types: [html_sitemap exclude=”portfolio,testimonials”]