wp_list_comments()WP 2.7.0

Outputs comments of posts (posts, pages). The function can accept a number of parameters and is used in the template to display a list of comments for a post/page. Some parameters can be configured in the admin panel.

This function itself does nothing. It should be called from the comments template file, for example, comments.php, which in turn should be included by the function comments_template().

Let's consider an example:

The file of the page/post where you want to insert comments:

<?php comments_template( '/comments.php' ); ?>

Then, in the comments.php file:

<div>
	<?php wp_list_comments(); ?>
</div>

Why should it be done this way? The function comments_template() must be called before this function. It retrieves comments from the database for output, which this function then uses. If this is not done, the second parameter $comments must be specified, where you indicate the comments you want to display.

Hooks from the function

Returns

null|String. HTML list of comments when the parameter echo=1. Null when the parameter echo=0.

Usage Template

<ul class="commentlist">
	<?php
	wp_list_comments( [
		'walker'            => null,
		'max_depth'         => '',
		'style'             => 'ul',
		'callback'          => null,
		'end-callback'      => null,
		'type'              => 'all',
		'reply_text'        => 'Reply',
		'page'              => '',
		'per_page'          => '',
		'avatar_size'       => 32,
		'reverse_top_level' => null,
		'reverse_children'  => '',
		'format'            => 'html5', // or xhtml if HTML5 is not supported by the theme
		'short_ping'        => false,    // Since version 3.6,
		'echo'              => true,     // true or false
	] );
	?>
</ul>

Usage

<?php wp_list_comments( $args, $comments ); ?>
$args(string/array)
Array of arguments that defines the output of comments.
Default: default parameters
$comments(array)
Array obtained from the function get_comments().
Default: $wp_query->comments

Arguments of the $args parameter

walker(object)
Instance of the class on which the list of comments is built. By default, the output is handled by the class Walker_Comment.
Default: null (new Walker_Comment)
max_depth(number)
The depth of nested child comments. Works if support for threaded comments is enabled. The parameter is set in the admin panel.
Default: 5
style(string)

How to display the list of comments. Can be div, ol, or ul. Keep in mind that the list itself needs to be wrapped manually:

<div class="commentlist">
 <?php wp_list_comments(array('style' => 'div')); ?>
</div>

or

<ol class="commentlist">
  <?php wp_list_comments(array('style' => 'ol')); ?>
</ol>

Default: 'ol'

callback(string)

Name of the function that will generate the HTML output of each comment.

The specified function will be called when outputting each comment. Keep in mind that the function must contain an opening tag <div> or <li> (depending on the style parameter), but this tag should not be closed. WordPress will automatically insert the closing tag. You can use the end-callback parameter to change it.

This separation is needed for threaded comments, where one comment is nested within another.

The function will receive three parameters $comment, $args, $depth. By default, the method is called:

Default: null

end-callback(string)

Name of the function that will generate the HTML output for closing each comment.

The specified function will be called when closing each comment. It should output the closing tag </div> or </li> (depending on the style parameter).

The callback and end-callback parameters are separated to correctly output threaded comments.

Default: null

type(string)
What type of comments to show. Can be all, comment, trackback, pingback, or pings. pings includes 'trackback' and 'pingback'.
Default: all
per_page(number)

Number of comments per page. The operation of this parameter is not so straightforward:

  • If the parameters page or per_page are passed and they are not equal to those specified in $wp_query: get_query_var('cpage') and get_query_var('comments_per_page'), a separate query will be created to retrieve all comments of the current post, and comments will be paginated.

  • If an empty string '' is specified and the option get_option( 'page_comments' ) is enabled, this parameter will equal the option get_query_var( 'comments_per_page' ).

  • If the parameter is empty (empty), then the parameters per_page and page will be set to 0;

Thus, when you need to display all comments (without pagination), specify the parameter: per_page=0 and page=1.

Default: 50

page(number)
Page of pagination whose comments need to be displayed.
avatar_size(number)
Size of the avatar in pixels.
Default: 32
reverse_top_level(boolean)
If set to true (1), the most recent comments will be at the top (reverse sorting by date). Affects only top-level comments (parent comments that have or can have child comments).
Default: false
reverse_children(boolean)
If set to true, the most recent comments will be at the top (reverse sorting by date). Affects only child comments.
Default: false
reply_text(string)
Text to be shown in the "reply" link. The text is used in the function: get_comment_reply_link().
Default: 'reply'
login_text(string)
Text that will be shown for unregistered users if comments can only be left by registered users.
Default: 'Log in to Reply'
echo(boolean)
Output the code to the screen or return it for processing.
Default: 'true'

Examples

2

#1 Example of using the walker parameter and creating your own class

An example of a comments.php file:

<?php
if ( post_password_required() ) {
	return;
}

if ( $comments ) {
	?>

	<div class="comments" id="comments">

		<?php
		wp_list_comments( [
			'walker'      => new TwentyTwenty_Walker_Comment(),
			'avatar_size' => 120,
			'style'       => 'div',
		] );
		?>

	</div><!-- comments -->

	<?php
}

Walker Class:

<?php

/**
 * CUSTOM COMMENT WALKER
 * A custom walker for comments, based on the walker in Twenty Nineteen.
 *
 * @since Twenty Twenty 1.0
 */
class TwentyTwenty_Walker_Comment extends Walker_Comment {

	/**
	 * Outputs a comment in the HTML5 format.
	 *
	 * @since Twenty Twenty 1.0
	 *
	 * @see wp_list_comments()
	 * @see https://developer.wordpress.org/reference/functions/get_comment_author_url/
	 * @see https://developer.wordpress.org/reference/functions/get_comment_author/
	 * @see https://developer.wordpress.org/reference/functions/get_avatar/
	 * @see https://developer.wordpress.org/reference/functions/get_comment_reply_link/
	 * @see https://developer.wordpress.org/reference/functions/get_edit_comment_link/
	 *
	 * @param WP_Comment $comment Comment to display.
	 * @param int        $depth   Depth of the current comment.
	 * @param array      $args    An array of arguments.
	 */
	protected function html5_comment( $comment, $depth, $args ) {

		$tag = 'div' === $args['style'] ? 'div' : 'li';

		?>
		<<?= $tag ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $this->has_children ? 'parent' : '', $comment ); ?>>
			<article id="div-comment-<?php comment_ID(); ?>" class="comment-body">
				<footer class="comment-meta">
					<div class="comment-author vcard">
						<?php
						$comment_author_url = get_comment_author_url( $comment );
						$comment_author     = get_comment_author( $comment );
						$avatar             = get_avatar( $comment, $args['avatar_size'] );
						if ( 0 !== $args['avatar_size'] ) {
							if ( empty( $comment_author_url ) ) {
								echo wp_kses_post( $avatar );
							} else {
								printf( '<a href="%s" rel="external nofollow" class="url">', $comment_author_url ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --Escaped in https://developer.wordpress.org/reference/functions/get_comment_author_url/
								echo wp_kses_post( $avatar );
							}
						}

						printf(
							'<span class="fn">%1$s</span><span class="screen-reader-text says">%2$s</span>',
							esc_html( $comment_author ),
							__( 'says:', 'twentytwenty' )
						);

						if ( ! empty( $comment_author_url ) ) {
							echo '</a>';
						}
						?>
					</div><!-- .comment-author -->

					<div class="comment-metadata">
						<?php
						/* translators: 1: Comment date, 2: Comment time. */
						$comment_timestamp = sprintf( __( '%1$s at %2$s', 'twentytwenty' ), get_comment_date( '', $comment ), get_comment_time() );

						printf(
							'<a href="%s"><time datetime="%s" title="%s">%s</time></a>',
							esc_url( get_comment_link( $comment, $args ) ),
							get_comment_time( 'c' ),
							esc_attr( $comment_timestamp ),
							esc_html( $comment_timestamp )
						);

						if ( get_edit_comment_link() ) {
							printf(
								' <span aria-hidden="true">•</span> <a class="comment-edit-link" href="%s">%s</a>',
								esc_url( get_edit_comment_link() ),
								__( 'Edit', 'twentytwenty' )
							);
						}
						?>
					</div><!-- .comment-metadata -->

				</footer><!-- .comment-meta -->

				<div class="comment-content entry-content">

					<?php

					comment_text();

					if ( '0' === $comment->comment_approved ) {
						?>
						<p class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'twentytwenty' ); ?></p>
						<?php
					}

					?>

				</div><!-- .comment-content -->

				<?php

				$comment_reply_link = get_comment_reply_link(
					array_merge(
						$args,
						array(
							'add_below' => 'div-comment',
							'depth'     => $depth,
							'max_depth' => $args['max_depth'],
							'before'    => '<span class="comment-reply">',
							'after'     => '</span>',
						)
					)
				);

				$by_post_author = twentytwenty_is_comment_by_post_author( $comment );

				if ( $comment_reply_link || $by_post_author ) {
					?>

					<footer class="comment-footer-meta">

						<?php
						if ( $comment_reply_link ) {
							echo $comment_reply_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Link is escaped in https://developer.wordpress.org/reference/functions/get_comment_reply_link/
						}
						if ( $by_post_author ) {
							echo '<span class="by-post-author">' . __( 'By Post Author', 'twentytwenty' ) . '</span>';
						}
						?>

					</footer>

					<?php
				}
				?>

			</article><!-- .comment-body -->

		<?php
	}
}
0

#2 Comment output using a custom function

To customize the appearance of each comment, you can use the callback parameter in which you specify the name of the function and then create that function with the desired code (output):

<ul class="commentlist">
	<?php 
	wp_list_comments( [ 
		'type'     => 'comment',
		'callback' => 'mytheme_comment',
	] );
	?>
</ul>

We display a list of comments -- type=comment -- i.e. they are not pings, and we also use our function that forms the appearance of the comment -- callback=mytheme_comment. Function mytheme_comment() should be described separately, you can add it in the file functions.php or directly in the same file where this code is located (usually it is comments.php).

<?php
function mytheme_comment( $comment, $args, $depth ) {

	if ( 'div' === $args['style'] ) {
		$tag       = 'div';
		$add_below = 'comment';
	}
	else {
		$tag       = 'li';
		$add_below = 'div-comment';
	}

	$classes = ' ' . comment_class( empty( $args['has_children'] ) ? '' : 'parent', null, null, false );
	?>

	<<?= $tag . $classes; ?> id="comment-<?php comment_ID() ?>">
	<?php if ( 'div' != $args['style'] ) { ?>
		<div id="div-comment-<?php comment_ID() ?>" class="comment-body"><?php
	} ?>

	<div class="comment-author vcard">
		<?php
		if ( $args['avatar_size'] != 0 ) {
			echo get_avatar( $comment, $args['avatar_size'] );
		}
		printf(
			__( '<cite class="fn">%s</cite> <span class="says">says:</span>' ),
			get_comment_author_link()
		);
		?>
	</div>

	<?php if ( $comment->comment_approved == '0' ) { ?>
		<em class="comment-awaiting-moderation">
			<?php _e( 'Your comment is awaiting moderation.' ); ?>
		</em><br/>
	<?php } ?>

	<div class="comment-meta commentmetadata">
		<a href="<?php echo htmlspecialchars( get_comment_link( $comment->comment_ID ) ); ?>">
			<?php
			printf(
				__( '%1$s at %2$s' ),
				get_comment_date(),
				get_comment_time()
			); ?>
		</a>

		<?php edit_comment_link( __( '(Edit)' ), '  ', '' ); ?>
	</div>

	<?php comment_text(); ?>

	<div class="reply">
		<?php
		comment_reply_link(
			array_merge(
				$args,
				array(
					'add_below' => $add_below,
					'depth'     => $depth,
					'max_depth' => $args['max_depth']
				)
			)
		); ?>
	</div>

	<?php if ( 'div' != $args['style'] ) { ?>
		</div>
	<?php }
}

IMPORTANT: the callback function must contain only the opening tag <li> and it must not be closed. See parameter description for details.

0

#3 Default use

Displays a list of comments. It is used in the comments.php template file. The presence of tree view and pagination in comments is controlled through the admin panel Settings > Discussion.

<ol class="commentlist">
	<?php wp_list_comments(); ?>
</ol>

Or customizing using parameters:

$cpage = get_query_var( 'cpage' ) ? get_query_var( 'cpage' ) : 1;

wp_list_comments(
	[
		'avatar_size'       => 60,
		'short_ping'        => true,
		'type'              => 'comment',
		'callback'          => 'ic_comment_list',
		'per_page'          => get_option( 'comments_per_page' ),
		'page'              => $cpage,
		'reverse_top_level' => get_option( 'default_comments_page' ) === 'oldest' ? false : true,
	]
);
0

#4 Display comments for a specific post

If you want to display the usual list of comments, but this list should include only the necessary comments.

Let's say we need to output the comments of a certain post. To do that, we'll first get the comments we want and then pass them to the second parameter of wp_list_comments() to output them in the usual format. The tree view, if set, is preserved.

<ol class="commentlist">
	<?php
	// Retrieve post comments with ID XXX from the database 
	$comments = get_comments( [
		'post_id' => XXX,
		'status' => 'approve' // moderated comments
	] );

	// generate a list of received comments
	wp_list_comments( [
		'per_page'          => 10,   // Pagination of comments - 10 per page
		'reverse_top_level' => false // Show last comments at the beginning
	], $comments );
	?>
</ol>
-14

#5 “Comment is awaiting moderation” alert.

If you are using WordPress 4.9.6 or higher and not showing “Comment is awaiting moderation” alert.

You can follow the steps below.

  1. Settings > Discussion > enable “Show comments cookies opt-in checkbox.”
  2. After activating checkbox will show on comments form like this. “Save my name, email, and website in this browser for the next time I comment.”

That’s it.

If you want to customize this checkbox field, you can use this code.

$comment_form = array(

	'fields' => array(
		'cookies' => '<p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes"' . $consent . ' />' .
		'<label for="wp-comment-cookies-consent">' . __( 'Save my name, email, and website in this browser for the next time I comment.' ) . '</label></p>',
	),
);

comment_form( $comment_form );

Notes

  • See: WP_Query::$comments
  • Global. WP_Query. $wp_query WordPress Query object.
  • Global. Int. $comment_alt
  • Global. Int. $comment_depth
  • Global. Int. $comment_thread_alt
  • Global. true|false. $overridden_cpage
  • Global. true|false. $in_comment_loop

Changelog

Since 2.7.0 Introduced.

wp_list_comments() code WP 6.9

function wp_list_comments( $args = array(), $comments = null ) {
	global $wp_query, $comment_alt, $comment_depth, $comment_thread_alt, $overridden_cpage, $in_comment_loop;

	$in_comment_loop = true;

	$comment_alt        = 0;
	$comment_thread_alt = 0;
	$comment_depth      = 1;

	$defaults = array(
		'walker'            => null,
		'max_depth'         => '',
		'style'             => 'ul',
		'callback'          => null,
		'end-callback'      => null,
		'type'              => 'all',
		'page'              => '',
		'per_page'          => '',
		'avatar_size'       => 32,
		'reverse_top_level' => null,
		'reverse_children'  => '',
		'format'            => current_theme_supports( 'html5', 'comment-list' ) ? 'html5' : 'xhtml',
		'short_ping'        => false,
		'echo'              => true,
	);

	$parsed_args = wp_parse_args( $args, $defaults );

	/**
	 * Filters the arguments used in retrieving the comment list.
	 *
	 * @since 4.0.0
	 *
	 * @see wp_list_comments()
	 *
	 * @param array $parsed_args An array of arguments for displaying comments.
	 */
	$parsed_args = apply_filters( 'wp_list_comments_args', $parsed_args );

	// Figure out what comments we'll be looping through ($_comments).
	if ( null !== $comments ) {
		$comments = (array) $comments;
		if ( empty( $comments ) ) {
			return;
		}
		if ( 'all' !== $parsed_args['type'] ) {
			$comments_by_type = separate_comments( $comments );
			if ( empty( $comments_by_type[ $parsed_args['type'] ] ) ) {
				return;
			}
			$_comments = $comments_by_type[ $parsed_args['type'] ];
		} else {
			$_comments = $comments;
		}
	} else {
		/*
		 * If 'page' or 'per_page' has been passed, and does not match what's in $wp_query,
		 * perform a separate comment query and allow Walker_Comment to paginate.
		 */
		if ( $parsed_args['page'] || $parsed_args['per_page'] ) {
			$current_cpage = (int) get_query_var( 'cpage' );
			if ( ! $current_cpage ) {
				$current_cpage = 'newest' === get_option( 'default_comments_page' ) ? 1 : $wp_query->max_num_comment_pages;
			}

			$current_per_page = (int) get_query_var( 'comments_per_page' );
			if ( (int) $parsed_args['page'] !== $current_cpage || (int) $parsed_args['per_page'] !== $current_per_page ) {
				$comment_args = array(
					'post_id' => get_the_ID(),
					'orderby' => 'comment_date_gmt',
					'order'   => 'ASC',
					'status'  => 'approve',
				);

				if ( is_user_logged_in() ) {
					$comment_args['include_unapproved'] = array( get_current_user_id() );
				} else {
					$unapproved_email = wp_get_unapproved_comment_author_email();

					if ( $unapproved_email ) {
						$comment_args['include_unapproved'] = array( $unapproved_email );
					}
				}

				$comments = get_comments( $comment_args );

				if ( 'all' !== $parsed_args['type'] ) {
					$comments_by_type = separate_comments( $comments );
					if ( empty( $comments_by_type[ $parsed_args['type'] ] ) ) {
						return;
					}

					$_comments = $comments_by_type[ $parsed_args['type'] ];
				} else {
					$_comments = $comments;
				}
			}

			// Otherwise, fall back on the comments from `$wp_query->comments`.
		} else {
			if ( empty( $wp_query->comments ) ) {
				return;
			}
			if ( 'all' !== $parsed_args['type'] ) {
				if ( empty( $wp_query->comments_by_type ) ) {
					$wp_query->comments_by_type = separate_comments( $wp_query->comments );
				}
				if ( empty( $wp_query->comments_by_type[ $parsed_args['type'] ] ) ) {
					return;
				}
				$_comments = $wp_query->comments_by_type[ $parsed_args['type'] ];
			} else {
				$_comments = $wp_query->comments;
			}

			if ( $wp_query->max_num_comment_pages ) {
				$default_comments_page = get_option( 'default_comments_page' );
				$cpage                 = (int) get_query_var( 'cpage' );

				if ( 'newest' === $default_comments_page ) {
					$parsed_args['cpage'] = $cpage;
				} elseif ( 1 === $cpage ) {
					/*
					 * When the first page shows the oldest comments,
					 * post permalink is the same as the comment permalink.
					 */
					$parsed_args['cpage'] = '';
				} else {
					$parsed_args['cpage'] = $cpage;
				}

				$parsed_args['page']     = 0;
				$parsed_args['per_page'] = 0;
			}
		}
	}

	if ( '' === $parsed_args['per_page'] && get_option( 'page_comments' ) ) {
		$parsed_args['per_page'] = get_query_var( 'comments_per_page' );
	}

	if ( empty( $parsed_args['per_page'] ) ) {
		$parsed_args['per_page'] = 0;
		$parsed_args['page']     = 0;
	}

	if ( '' === $parsed_args['max_depth'] ) {
		if ( get_option( 'thread_comments' ) ) {
			$parsed_args['max_depth'] = get_option( 'thread_comments_depth' );
		} else {
			$parsed_args['max_depth'] = -1;
		}
	}

	if ( '' === $parsed_args['page'] ) {
		if ( empty( $overridden_cpage ) ) {
			$parsed_args['page'] = get_query_var( 'cpage' );
		} else {
			$threaded            = ( -1 !== (int) $parsed_args['max_depth'] );
			$parsed_args['page'] = ( 'newest' === get_option( 'default_comments_page' ) ) ? get_comment_pages_count( $_comments, $parsed_args['per_page'], $threaded ) : 1;
			set_query_var( 'cpage', $parsed_args['page'] );
		}
	}

	// Validation check.
	$parsed_args['page']     = (int) $parsed_args['page'];
	$parsed_args['per_page'] = (int) $parsed_args['per_page'];
	if ( 0 === $parsed_args['page'] && 0 !== $parsed_args['per_page'] ) {
		$parsed_args['page'] = 1;
	}

	if ( null === $parsed_args['reverse_top_level'] ) {
		$parsed_args['reverse_top_level'] = ( 'desc' === get_option( 'comment_order' ) );
	}

	if ( empty( $parsed_args['walker'] ) ) {
		$walker = new Walker_Comment();
	} else {
		$walker = $parsed_args['walker'];
	}

	$output = $walker->paged_walk( $_comments, $parsed_args['max_depth'], $parsed_args['page'], $parsed_args['per_page'], $parsed_args );

	$in_comment_loop = false;

	if ( $parsed_args['echo'] ) {
		echo $output;
	} else {
		return $output;
	}
}