Dynamic HTML sitemap in WordPress with shortcode

Jul 18, 2025

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

Blog Posts

Products

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: