Content Marketing

WordPress: How to List Child Pages Using A Shortcode

We’ve rebuilt the hierarchy of sites for several of our WordPress clients, and one of the things we attempt to do is organize the information efficiently. To achieve this, we often want to create a master page that includes a menu automatically listing the pages below it, a list of child pages, or subpages.

Unfortunately, WordPress does not have an inherent function or feature to do this, so we developed a shortcode to add to the client’s site. Here’s how you can use the shortcode with all its variables populated within a WordPress post or page:

[listchildpages ifempty="No child pages found" order="ASC" orderby="title" ulclass="custom-ul-class" liclass="custom-li-class" aclass="custom-a-class" displayimage="yes" align="aligncenter"]

Breakdown of Usage:

  • ifempty="No child pages found": This text will be displayed if there are no child pages available.
  • order="ASC": This sorts the list of child pages in ascending order.
  • orderby="title": This orders the child pages by their title.
  • ulclass="custom-ul-class": Applies the CSS class “custom-ul-class” to the <ul> element of the list.
  • liclass="custom-li-class": Applies the CSS class “custom-li-class” to each <li> element in the list.
  • aclass="custom-a-class": Applies the CSS class “custom-a-class” to each <a> (link) element in the list.
  • displayimage="yes": This includes the featured image of each child page in the list.
  • align="aligncenter": This aligns the featured images in the center.

Insert this shortcode directly into the content area of a WordPress post or page where you want the list of child pages to appear. Remember to customize the values of each attribute to fit the design and structure of your WordPress site.

Additionally, if you’d like a short excerpt describing each page, the plugin enables excerpts on pages so that you can edit that content on the page’s settings.

List Child Pages Shortcode Plugin


Create a folder named mtz-list-child-pages and add a single file inside it called mtz-list-child-pages.php with the following contents.

<?php
/**
 * Plugin Name: List Child Pages
 * Description: Outputs a list of child pages for a given parent page with optional thumbnails and classes. Safe when used outside the main Loop. Shortcode: [listchildpages].
 * Version: 1.0.0
 * Author: Douglas Karr
 * Author URI: https://dknewmedia.com
 * License: GPLv2 or later
 * Text Domain: mtz-list-child-pages
 */

defined( 'ABSPATH' ) || exit;

if ( ! function_exists( 'mtz_listchildpages' ) ) :

/**
 * Shortcode: [listchildpages ifempty="<p>No Records</p>" order="DESC" orderby="date" ulclass="" liclass="" aclass="" displayimage="no" align="alignleft" parent=""]
 *
 * Attributes:
 * - ifempty: Fallback HTML if no children are found.
 * - order: ASC or DESC. Defaults to DESC.
 * - orderby: One of date, title, menu_order, modified, ID, rand, name, type, author.
 *            Legacy aliases publish_date and published are mapped to date.
 * - ulclass, liclass, aclass: Extra CSS classes for the wrapper <ul>, each <li>, and the <a>.
 * - displayimage: yes/no (accepts y, yes, t, true, 1, on).
 * - align: CSS class to place on the <img> tag when displayimage is true.
 * - parent: A specific parent page ID. If omitted, uses the queried object when possible.
 */
function mtz_listchildpages( $atts, $content = '' ) {
	$string = '';

	$atts = shortcode_atts(
		array(
			'ifempty'      => '<p>No Records</p>',
			'order'        => 'DESC',
			'orderby'      => 'publish_date', // legacy alias; mapped to 'date' below
			'ulclass'      => '',
			'liclass'      => '',
			'aclass'       => '',
			'displayimage' => 'no',
			'align'        => 'alignleft',
			'parent'       => '',
		),
		$atts,
		'listchildpages'
	);

	// Resolve parent ID safely.
	$parent_id = 0;

	// 1) Respect explicit parent attribute if valid.
	if ( $atts['parent'] !== '' ) {
		$maybe = absint( $atts['parent'] );
		if ( $maybe > 0 ) {
			$parent_id = $maybe;
		}
	}

	// 2) Fall back to the queried object ID.
	if ( ! $parent_id ) {
		$queried_id = get_queried_object_id();
		if ( $queried_id ) {
			$parent_id = $queried_id;
		}
	}

	if ( ! $parent_id ) {
		return $atts['ifempty'];
	}

	// Normalize/whitelist 'orderby' and 'order'.
	$orderby_input = strtolower( trim( (string) $atts['orderby'] ) );
	$orderby_map   = array(
		'publish_date' => 'date',
		'published'    => 'date',
		'date'         => 'date',
		'title'        => 'title',
		'menu_order'   => 'menu_order',
		'modified'     => 'modified',
		'id'           => 'ID',
		'rand'         => 'rand',
		'name'         => 'name',
		'type'         => 'type',
		'author'       => 'author',
	);
	$orderby       = isset( $orderby_map[ $orderby_input ] ) ? $orderby_map[ $orderby_input ] : 'date';

	$order_input = strtoupper( trim( (string) $atts['order'] ) );
	$order       = in_array( $order_input, array( 'ASC', 'DESC' ), true ) ? $order_input : 'DESC';

	$args = array(
		'post_type'      => 'page',
		'posts_per_page' => -1,
		'post_parent'    => $parent_id,
		'orderby'        => $orderby,
		'order'          => $order,
		'no_found_rows'  => true, // performance optimization
		'fields'         => 'ids', // fetch IDs first
	);

	$q = new WP_Query( $args );

	if ( ! $q->have_posts() ) {
		wp_reset_postdata();
		return $atts['ifempty'];
	}

	$showimage = in_array( strtolower( (string) $atts['displayimage'] ), array( 'y', 'yes', 't', 'true', '1', 'on' ), true );

	// Begin output.
	$string .= $content;
	$string .= '<ul class="' . esc_attr( $atts['ulclass'] ) . '">';

	foreach ( $q->posts as $child_id ) {
		$title = get_the_title( $child_id );
		$perma = get_permalink( $child_id );

		$string .= '<li class="' . esc_attr( $atts['liclass'] ) . '">';

		// Optional thumbnail.
		if ( $showimage && has_post_thumbnail( $child_id ) ) {
			$thumb = wp_get_attachment_image_src( get_post_thumbnail_id( $child_id ), 'thumbnail' );
			if ( $thumb && is_array( $thumb ) ) {
				$src    = $thumb[0];
				$width  = (int) $thumb[1];
				$height = (int) $thumb[2];
				$string .= '<a class="' . esc_attr( $atts['aclass'] ) . '" href="' . esc_url( $perma ) . '" title="' . esc_attr( $title ) . '">';
				$string .= '<img src="' . esc_url( $src ) . '" width="' . $width . '" height="' . $height . '" alt="' . esc_attr( $title ) . '" class="' . esc_attr( $atts['align'] ) . '" />';
				$string .= '</a>';
			}
		}

		// Title link.
		$string .= '<a class="' . esc_attr( $atts['aclass'] ) . '" href="' . esc_url( $perma ) . '" title="' . esc_attr( $title ) . '">';
		$string .= esc_html( $title ) . '</a>';

		// Optional excerpt.
		if ( has_excerpt( $child_id ) ) {
			$excerpt = get_the_excerpt( $child_id );
			if ( $excerpt !== '' ) {
				$string .= ' - ' . esc_html( wp_strip_all_tags( $excerpt ) );
			}
		}

		$string .= '</li>';
	}

	$string .= '</ul>';

	wp_reset_postdata();
	return $string;
}

endif;

// Register shortcode.
add_shortcode( 'listchildpages', 'mtz_listchildpages' );

Here’s a straightforward, step-by-step installation guide for the plugin:

  1. Download or create the plugin file: Copy the full plugin code into a new file named mtz-list-child-pages.php.
  2. Create a plugin folder: Inside your WordPress installation, go to wp-content/plugins/ and create a new folder named mtz-list-child-pages.
  3. Place the file: Save the mtz-list-child-pages.php file inside the mtz-list-child-pages folder.
  4. Log into WordPress admin: Open your site’s WordPress dashboard (/wp-admin).
  5. Go to the Plugins screen: In the left sidebar, click on Plugins > Installed Plugins.
  6. Activate the plugin: Find “List Child Pages” in the list and click Activate.
  7. Use the shortcode: Add the shortcode to any page, post, or widget area.

Douglas Karr

Douglas Karr is a fractional Chief Marketing Officer specializing in SaaS and AI companies, where he helps scale marketing operations, drive demand generation, and implement AI-powered strategies. He is the founder and publisher of Martech Zone, a leading publication in marketing technology, and a trusted advisor to startups and enterprises… More »
Back to top button
Close

Adblock Detected

We rely on ads and sponsorships to keep Martech Zone free. Please consider disabling your ad blocker—or support us with an affordable, ad-free annual membership ($10 US):

Sign Up For An Annual Membership