Comments in WordPress

Comments are a separate entity in the WordPress data structure. They link comments to posts (entries, wp_posts table) and users (wp_users table).

Structure of comments tables in the database

In the WordPress database, comments are managed by two tables: wp_comments and wp_commentmeta. Let's analyze each one.

See the full table schema.

wp_comments

Contains the main comment data.

Field Description and example value
comment_ID Comment ID. (7999)
comment_post_ID ID of the post to which the comment belongs. Equals the ID column in the wp_posts table. (4896)
comment_author Comment author's name. (Mseo)
comment_author_email Comment author's email. ([email protected])
comment_author_url Comment author's URL. (http://maeo.ru)
comment_author_IP Comment author's IP. Not automatically determined. (95.79.52.2)
comment_approved Whether the comment is approved: 1/0. This is a string field and plugins can write other values to it, for example, 'spam'. It would be more correct to name this field comment_status, but it is used in the wp_posts table.
comment_agent Comment author's user agent. (Mozilla/5.0 (Windows...)
comment_date Date and time the comment was submitted. In MySQL format. (2015-09-01 16:28:33)
comment_date_gmt Date and time the comment was submitted in GMT zone. (2015-09-01 16:28:33)
comment_content Comment text. (Hello, the above-mentioned...)
comment_karma Comment karma. (0)
comment_type Comment type. By default, WP uses three types: comment - regular comment (was an empty string '' before version 5.0). trackback and pingback. Read about them here.
comment_parent ID of the parent comment. (7998)
user_id ID of the user who published the comment. Equals the ID column in the wp_users table (123)

IMPORTANT! The length of the comment_type field value = 20 characters! Example of the maximum allowable length: new_question_comment.

An example of how such a limitation can ruin the code: let's say we have an additional comment type question_comment. Over time, some of these comments end up in the archive, and we need to add the prefix archive- to the type. We do it like this:

// $com - this is the object of the processed comment
wp_update_comment( [
	'comment_ID'   => $com->comment_ID,
	'comment_type' => "archive-$com->comment_type", // archive-question_comment
] );

As a result, this update will not work, and more importantly, we won't see any errors - nothing! To understand why it's not updating, we'll have to delve into the depths of the WP core.

wp_commentmeta

Contains additional comment data. By default, nothing is written to this table. It is used to extend the commenting functionality and add additional data to comments.

Displaying comments in the theme

To display comments on a page, you need to use the function comments_template() where you want to show the list of comments. This function will make a request and collect the comments for the page.

Also, the theme needs to have a file named comments.php. In this file, you need to describe how to display comments:

  • Check if there are comments on the page. This is done using the $comemnts variable, which is set in comments_template() - $comments = & $wp_query->comments and is available in this file. You can also check for the presence of comments on the page using the function have_comments(). Then:
    • if there are comments, use the function wp_list_comments() to display the list of comments.
    • if there are no comments, display a message that there are none.
  • Display the commenting form. See comment_form().

Example of the comments.php file from the Twenty Twenty theme:

<?php
/**
 * The template file for displaying the comments and comment form for the
 * Twenty Twenty theme.
 *
 * @package WordPress
 * @subpackage Twenty_Twenty
 * @since Twenty Twenty 1.0
 */

/*
 * If the current post is protected by a password and
 * the visitor has not yet entered the password we will
 * return early without loading the comments.
*/
if ( post_password_required() ) {
	return;
}

if ( $comments ) {
	?>

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

		<?php
		$comments_number = absint( get_comments_number() );
		?>

		<div class="comments-header section-inner small max-percentage">

			<h2 class="comment-reply-title">
				<?php
				if ( ! have_comments() ) {
					_e( 'Leave a comment', 'twentytwenty' );
				}
				elseif ( 1 === $comments_number ) {
					/* translators: %s: Post title. */
					printf( _x( 'One reply on “%s”', 'comments title', 'twentytwenty' ), get_the_title() );
				}
				else {
					printf(
					/* translators: 1: Number of comments, 2: Post title. */
						_nx(
							'%1$s reply on “%2$s”',
							'%1$s replies on “%2$s”',
							$comments_number,
							'comments title',
							'twentytwenty'
						),
						number_format_i18n( $comments_number ),
						get_the_title()
					);
				}

				?>
			</h2><!-- .comments-title -->

		</div><!-- .comments-header -->

		<div class="comments-inner section-inner thin max-percentage">

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

			$comment_pagination = paginate_comments_links(
				array(
					'echo'      => false,
					'end_size'  => 0,
					'mid_size'  => 0,
					'next_text' => __( 'Newer Comments', 'twentytwenty' ) . ' <span aria-hidden="true">→</span>',
					'prev_text' => '<span aria-hidden="true">←</span> ' . __( 'Older Comments', 'twentytwenty' ),
				)
			);

			if ( $comment_pagination ) {
				$pagination_classes = '';

				// If we're only showing the "Next" link, add a class indicating so.
				if ( false === strpos( $comment_pagination, 'prev page-numbers' ) ) {
					$pagination_classes = ' only-next';
				}
				?>

				<nav class="comments-pagination pagination<?php echo $pagination_classes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static output ?>" aria-label="<?php esc_attr_e( 'Comments', 'twentytwenty' ); ?>">
					<?php echo wp_kses_post( $comment_pagination ); ?>
				</nav>

				<?php
			}
			?>

		</div><!-- .comments-inner -->

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

	<?php
}

if ( comments_open() || pings_open() ) {

	if ( $comments ) {
		echo '<hr class="styled-separator is-style-wide" aria-hidden="true" />';
	}

	comment_form(
		array(
			'class_form'         => 'section-inner thin max-percentage',
			'title_reply_before' => '<h2 id="reply-title" class="comment-reply-title">',
			'title_reply_after'  => '</h2>',
		)
	);

}
elseif ( is_single() ) {

	if ( $comments ) {
		echo '<hr class="styled-separator is-style-wide" aria-hidden="true" />';
	}

	?>

	<div class="comment-respond" id="respond">

		<p class="comments-closed"><?php _e( 'Comments are closed.', 'twentytwenty' ); ?></p>

	</div><!-- #respond -->

	<?php
}

When an unauthenticated user leaves a comment, WP sets cookies to store the entered data. The following cookies are saved:

'comment_author_' . COOKIEHASH
'comment_author_email_' . COOKIEHASH
'comment_author_url_' . COOKIEHASH

// where COOKIEHASH = md5( get_site_option( 'siteurl' ) )

These cookies are saved by the function wp_set_comment_cookies().

By default, this function is hooked to the set_comment_cookies hook in the file /wp-includes/default-filters.php:

add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 );

This hook is triggered when a comment is posted through the commenting form comment_form(). That is, when a POST request is sent to the file /wp-comments-post.php:

/**
 * Perform other actions when comment cookies are set.
 *
 * @since 3.4.0
 * @since 4.9.6 The `$cookies_consent` parameter was added.
 *
 * @param WP_Comment $comment         Comment object.
 * @param WP_User    $user            Comment author's user object. The user may not exist.
 * @param bool       $cookies_consent Comment author's consent to store cookies.
 */
do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );

Use wp_get_current_commenter() to obtain the data of these cookies.

To use this function, the option show_comments_cookies_opt_in must be enabled: "Discussion" → checkbox "Show consent to store cookies for commenters".

When this option is enabled, the commenting form will display a checkbox. If the checkbox is checked, the field $_POST['wp-comment-cookies-consent'] is sent with the request. This option is subsequently passed as the third parameter to the function wp_set_comment_cookies().

Comment functions

This is a list of frequently used functions. See the full list here.

comments_template() Load the comment template specified in $file.
wp_list_comments() List comments. Used in the comments.php template to list comments for a particular post.
comment_form() Outputs a complete commenting form for use within a template.
get_comments() Retrieve a list of comments.
get_comment() Retrieves comment data given a comment ID or comment object.
get_comments_number() Retrieves the amount of comments a post has.
wp_count_comments() Retrieve total comments for blog or single post.
get_comment_date() Retrieve the comment date of the current comment.
get_avatar_url() Retrieves the avatar URL.
comment_reply_link() Displays the HTML content for reply to comment link.
edit_comment_link() Displays the edit comment link with formatting.
comment_link() Display the link to the comments.
paginate_comments_links() Displays or retrieves pagination links for the comments on the current post.
wp_insert_comment() Inserts/adds a comment into the database.
wp_new_comment() Adds a new comment to the database.