2026年11月12日
2026年11月12日
Customize WordPress Comments with a wp_list_comments Callback
This article is a localized edition of the Japanese original. Read the Japanese original
In a classic theme, the WordPress comment section is built from the comments.php template, wp_list_comments(), which outputs the list of comments, and comment_form(), which outputs the form. If you pass a callback function to wp_list_comments(), you can build the HTML for each comment however you like. The functions and arguments in this article were checked against the function reference on developer.wordpress.org and the WordPress source code (wp-includes/comment-template.php, class-walker-comment.php) as of November 2026.
Block themes (such as the default themes from Twenty Twenty-Two onward) do not use comments.php; you build the comment display with the "Comments" block in the Site Editor. This article is for classic themes. Edits made directly to theme files are lost when the theme updates, so work in a child theme.
A basic comments.php
Calling comments_template() in a single-post template (such as single.php) loads the theme's comments.php. Here is a basic version that uses a callback function.
<?php
// comments.php
// On a password-protected post, output nothing until the password has been entered
if ( post_password_required() ) {
return;
}
?>
<div id="comments" class="comments-area">
<?php if ( have_comments() ) : ?>
<h2 class="comments-title">
<?php comments_number( 'No comments yet', '1 comment', '% comments' ); ?>
</h2>
<ol class="comment-list">
<?php
wp_list_comments(
array(
'style' => 'ol',
'callback' => 'mytheme_comment',
'avatar_size' => 48,
)
);
?>
</ol>
<?php
the_comments_pagination(
array(
'prev_text' => 'Previous comments',
'next_text' => 'Next comments',
)
);
?>
<?php endif; ?>
<?php if ( ! comments_open() && get_comments_number() ) : ?>
<p class="no-comments">Comments are closed for this post.</p>
<?php endif; ?>
<?php comment_form( mytheme_comment_form_args() ); ?>
</div>
- The three arguments of
comments_number()are the text for zero, one, and two or more comments.%is replaced with the count. the_comments_pagination()outputs links only when you have chosen to break comments into pages in "Settings" → "Discussion" (設定 → ディスカッション in the Japanese UI). The number per page is set on the same screen.comment_form()outputs nothing on posts that are closed to comments.
Build the HTML for each comment with a callback
The function you pass as the callback of wp_list_comments() is called once per comment with three arguments: ( $comment, $args, $depth ). This function outputs the opening tag (such as <li>), but not the closing tag. WordPress adds </li> after it has output the child comments (replies).
<?php
// functions.php
function mytheme_comment( $comment, $args, $depth ) {
$tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
// Show pingbacks and trackbacks as a simple one-liner
if ( in_array( $comment->comment_type, array( 'pingback', 'trackback' ), true ) ) {
?>
<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( 'pingback', $comment ); ?>>
<div class="comment-body">Pingback: <?php comment_author_link( $comment ); ?></div>
<?php
return;
}
$post = get_post( $comment->comment_post_ID );
$is_post_author = $post && $comment->user_id && (int) $comment->user_id === (int) $post->post_author;
$parent_class = empty( $args['has_children'] ) ? '' : 'parent';
?>
<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $parent_class, $comment ); ?>>
<article id="div-comment-<?php comment_ID(); ?>" class="comment-body">
<header class="comment-meta">
<div class="comment-author vcard">
<?php
if ( 0 !== (int) $args['avatar_size'] ) {
echo get_avatar( $comment, $args['avatar_size'] );
}
?>
<span class="fn"><?php comment_author_link( $comment ); ?></span>
<?php if ( $is_post_author ) : ?>
<span class="post-author-badge">Author</span>
<?php endif; ?>
</div>
<a class="comment-date" href="<?php echo esc_url( get_comment_link( $comment ) ); ?>">
<time datetime="<?php comment_time( 'c' ); ?>">
<?php comment_date( 'F j, Y', $comment ); ?> <?php comment_time( 'H:i' ); ?>
</time>
</a>
<?php if ( '0' === $comment->comment_approved ) : ?>
<p class="comment-awaiting-moderation">Your comment is awaiting moderation.</p>
<?php endif; ?>
</header>
<div class="comment-content">
<?php comment_text(); ?>
</div>
<?php
comment_reply_link(
array_merge(
$args,
array(
'add_below' => 'div-comment',
'depth' => $depth,
'max_depth' => $args['max_depth'],
'before' => '<div class="reply">',
'after' => '</div>',
)
)
);
?>
</article>
<?php
// Do not output </li> (WordPress adds it)
}
Key points in the code:
- Reply link: Match the
add_belowofcomment_reply_link()with the first part of theidon the element wrapping the comment body (div-comment-コメントID, that is, div-comment- followed by the comment ID). When someone clicks reply, the form moves below this element.max_depthis the nesting depth set in the threaded (nested) comments setting in "Settings" → "Discussion"; comments at that depth get no reply link. - Post-author badge: For comments the post author wrote while logged in,
comment_class()automatically adds thebypostauthorclass. If you only want to change the look, you can use CSS such as.bypostauthor > .comment-body { … }. The example above also prints a text badge, so it compares the comment's user ID with the post author's ID after casting both to integers (values read from the database are strings, so comparing them as is with===does not match). - Awaiting moderation:
comment_approvedis the string'0', and a comment awaiting moderation is shown only in the browser of the person who wrote it. - Avatar: Setting
avatar_sizeto0hides the avatar in this example. If avatars are turned off in "Settings" → "Discussion",get_avatar()returns nothing.
Load the reply script
To move the form below the comment when someone clicks a reply link, load the comment-reply script bundled with WordPress. In a classic theme, the theme has to load it.
<?php
// functions.php
add_action( 'wp_enqueue_scripts', function () {
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
wp_enqueue_script( 'comment-reply' );
}
} );
Without it, replying still works, but the page reloads and the form stays at the bottom of the page.
Change the comment form text
Passing an array of arguments to comment_form() lets you change the heading and button text. Here is the mytheme_comment_form_args() used in comments.php.
<?php
// functions.php
function mytheme_comment_form_args() {
return array(
'title_reply' => 'Leave a comment',
'title_reply_to' => 'Reply to %s', // %s is the name being replied to
'cancel_reply_link' => 'Cancel reply',
'label_submit' => 'Post comment',
'comment_notes_before' => '<p class="comment-notes">Your email address will not be published.</p>',
'comment_field' => '<p class="comment-form-comment"><label for="comment">Comment</label><textarea id="comment" name="comment" cols="45" rows="6" maxlength="65525" required></textarea></p>',
);
}
// Remove the "Website" field
add_filter( 'comment_form_default_fields', function ( $fields ) {
unset( $fields['url'] );
return $fields;
} );
- For the name and email fields, removing or changing only what you need with the
comment_form_default_fieldsfilter, as above, is safer than replacing them all withfields. The default fields include thevaluethat refills the previously entered name and email, and the required-field indicators. - Do not change the
nameattributes of the fields (author,email,url,comment). WordPress reads the values by these names. - The checkbox "Save my name, email, and website in this browser for the next time I comment." appears when it is enabled in "Settings" → "Discussion". Even if you pass
fields, WordPress adds this field when it is missing.
How to add a field and save and display its value is covered in "How to customize the WordPress comment form".
Check the result
- On a post that accepts comments, post comments both logged out and logged in, and check the list and the awaiting-moderation message.
- Click a reply link and check that the form moves below that comment and returns with "Cancel reply".
- Post a comment from the post author's account and check that the badge appears.
- Turn on paged comments in "Settings" → "Discussion" and check that the pagination links appear.
Related articles
- The overall structure of
comments.phpand the conditions for showing the comment section are also covered in "How to customize the WordPress comment template (comments.php)". - For dealing with comment spam, see "Fighting WordPress comment spam".
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)