Using the WordPress comment form

In past versions of WordPress, theme developers had to create all the HTML for handling comment forms. While this wasn’t a tough task, it was a stiff and inflexible system that didn’t offer a lot in the way of innovation. Plus, plugin authors only had a single hook to work with, so potentially awesome comment plugins were not something easily built without having users manually input code.

With the addition of the comment meta table in WordPress 2.9, the problem became even more apparent. What good was comment meta if plugin authors had no way to take advantage of it?

WordPress 3.0 introduced a new function for handling comment forms: comment_form(). Theme developers, listen up. If your theme isn’t using this function, you’re probably going to have some angry plugin developers bugging you at some point.

In this tutorial, I’m going to walk you through the steps of using the comment form and theme/plugin developers can manipulate its output.

The basic function

Standard comment forms in the past would look something like the below code.

<div id="respond">

	Lots of HTML code to output form elements.


If you’re updating a theme, you’ll need to delete all of that comment form code and replace it with a single line.

<?php comment_form(); ?>

It does not get any simpler than that. If all you’re wanting to do is update your comment form to the new standard, you can stop reading now. That’s all the required code you need. You may have to tinker with your stylesheet to make sure the classes/IDs match with any changes, but you don’t have to do anything else.

Comment form arguments

The comment_form function takes in two parameters by default.

comment_form( $args, $post_id );
  • $args: An array of arguments for controlling the output of the comment form.
  • $post_id: The ID of the post for which to show a comment form. This defaults to the current post, so you don’t need it in most scenarios.

The $args variable is what we want to focus on. There are two ways to change this. The first is to manually input the arguments into the comment_form() function. The second is to filter the output (see “Filtering the comment form defaults” below).

Input of these arguments would look something like this:

<?php comment_form(
		'cancel_reply_link' => __( 'Cancel reply' ),
		'label_submit' => __( 'Post Comment' ),
); ?>

Next, I’ll list all the arguments and explain what they’re used for.


The fields argument is an array of multiple fields that come standard in the comment form: author, email, and url. You can also add custom input fields here if you like, but we’re going to focus on the defaults for now.

These fields are only shown for users that are not logged in. It is assumed that these fields represent information that logged-in users have already provided through their profile page in the admin.

If you’re going to customize these fields, you need to add a small bit of code before the comment form for getting a few variables.

	$commenter = wp_get_current_commenter();
	$req = get_option( 'require_name_email' );

When creating custom fields, the most important thing is to stay consistent with the HTML for easier styling.

'fields' => array(
	'author' => '<p class="comment-form-author">' . '<label for="author">' . __( 'Name' ) . '</label> ' . ( $req ? '<span class="required">*</span>' : '' ) . '<input id="author" name="author" type="text" value="' . esc_attr( $commenter['comment_author'] ) . '" size="30" /></p>',
	'email' => '<p class="comment-form-email"><label for="email">' . __( 'Email' ) . '</label> ' . ( $req ? '<span class="required">*</span>' : '' ) . '<input id="email" name="email" type="text" value="' . esc_attr(  $commenter['comment_author_email'] ) . '" size="30" /></p>',
	'url' => '<p class="comment-form-url"><label for="url">' . __( 'Website' ) . '</label>' . '<input id="url" name="url" type="text" value="' . esc_attr( $commenter['comment_author_url'] ) . '" size="30" /></p>',


The comment_field argument allows you to customize the HTML of the comment textarea (where the actual comment text is input).

'comment_field' => '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) . '</label><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true"></textarea></p>',


To change the text shown when a user must log in to comment, you use the must_log_in argument. Note that you definitely need to use the standard wp_login_url() function to show a link for logging in. Otherwise, plugins won’t be able to hook into it properly.

'must_log_in' => '<p class="must-log-in">' .  sprintf( __( 'You must be [logged in](%s) to post a comment.' ), wp_login_url( get_permalink() ) ) . '</p>',


You can easily change the text shown when a user is logged in using the logged_in_as argument. Note that you should also link to the user account with the admin_url() function and use the standard wp_logout_url() function for providing a link to log out of the site.

'logged_in_as' => '<p class="logged-in-as">' . sprintf( __( 'Logged in as [%2$s](%1$s). [Log out?](%3$s)' ), admin_url( 'profile.php' ), $user_identity, wp_logout_url( get_permalink() ) ) . '</p>',


The comment_notes_before argument allows you to add custom HTML before the comment form fields. This is only shown to users that are not logged in.

'comment_notes_before' => '<p class="comment-notes">' . __( 'Your email adress will not be published.' ) . '</p>',


The comment_notes_after argument is shown to all users (logged in or not) after the comment form fields and textarea.

'comment_notes_after' => '<p class="form-allowed-tags">' . sprintf( __( 'You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: %s' ), ' `' . allowed_tags() . '`' ) . '</p>',


The id_form argument allows the input of a custom HTML ID for the <form> element.

'id_form' => 'commentform',


The id_submit argument allows the input of a custom HTML ID for the submit button.

'id_submit' => 'submit',


You can change the text for the submit button with the label_submit argument.

'label_submit' => __( 'Post Comment' ),


Before the comment form a reply title is shown letting readers know they can reply. You can change its text with the title_reply argument.

'title_reply' => __( 'Leave a Reply' ),


When users are using nested comments and a reader clicks the “reply” link on a comment, the reply title changes slightly. You can use the title_reply_to argument to customize this text.

'title_reply_to' => __( 'Leave a Reply to %s' ),


When nested comments are turned on and a reader is replying to a comment, a cancel link is shown next to the reply title. The cancel_reply_link argument allows you to change the text shown for this.

'cancel_reply_link' => __( 'Cancel reply' ),

Filtering the comment form defaults

If you’re adding a lot of customizations to the comment_form as a theme developer, you probably noticed how quickly your code started looking messy. Generally, I don’t like for users to see all of that and potentially mess it up and take down their entire site. I highly recommend using comment_form_defaults or one of the other more-specific hooks for heavy customizations.

These filter hooks ares also what plugin authors will use to manipulate the comment form.


If you just need to change the output of the fields argument from above or add extra inputs, you would use this hook. The example below will add an input field for a Twitter username.

add_filter( 'comment_form_default_fields', 'my_comment_form_default_fields' );

function my_comment_form_default_fields( $fields ) {

	$fields['twitter'] = '<p class="comment-form-twitter"><label for="twitter">' . __( 'Twitter (@username)' ) . '</label><input type="text" id="twitter" name="twitter" value="" size="30" /></p>';

	return $fields;


You can also filter a specific default field by its name. The $name variable represents the array key used for the specific field. For example, you may want to change the <p> class for the author field.

add_filter( 'comment_form_field_author', 'my_comment_form_field_author' );

function my_comment_form_field_author( $field ) {

	$field = str_replace( 'class="comment-form-author"', 'class="form-author"', $field );

	return $field;


The comment_field argument from above also has a specific filter for overwriting it just before display. Let’s suppose we wanted to add a wrapper <div> around it because we have to do some funky CSS design.

add_filter( 'comment_form_field_comment', 'my_comment_form_field_comment' );

function my_comment_form_field_comment( $comment_field ) {

	$comment_field = '<div class="comment-field-wrapper">' . $comment_field . '</div>';

	return $comment_field;


This hook allows you to filter what’s displayed for the logged_in_as argument from above. It passes three parameters:

  • $logged_in_as: The HTML used for displaying logged-in information.
  • $commenter: Information about the current comment provided by the wp_get_current_commenter() function.
  • $user_identity: The global variable used in WordPress that represents the current user.

There’s a lot of things you can do with this, such as creating custom messages for the logged-in user. But, let’s keep this example simple and append a small message to the end of the normal output.

add_filter( 'comment_form_logged_in', 'my_comment_form_logged_in', 10, 3 );

function my_comment_form_logged_in( $logged_in_as, $commenter, $user_identity ) {

	$logged_in_as .= '<p>' . __( 'We appreciated comments from registered users and are happy to read your feedback.' ) . '</p>';

	return $logged_in_as;


If you need to overwrite arguments other than the default fields, you’d use the comment_form_defaults filter hook. You can use this hook to overwrite any of the arguments we went over above. In the example below, we’re going to change the submit button text to read “Submit.”

add_filter( 'comment_form_defaults', 'my_comment_form_defaults' );

function my_comment_form_defaults( $defaults ) {

	$defaults['label_submit'] = __( 'Submit' );

	return $defaults;

Comment form action hooks

If you didn’t think there were enough filter hooks available for customization, take a look at these extra action hooks. There’s plenty of them to accommodate anyone’s needs.

I’m only going to give you one code example of using the hooks because the concept and method of using them is the same for each hook.


This hook is fired only when comments are closed. It is also the only action hook available to developers when they’re closed. The other hooks will only be available when comments are open.

In this example, we’ll show a message to let readers know that commenting is now closed.

add_action( 'comment_form_comments_closed', 'my_comments_closed' );

function my_comments_closed() {
	echo '<p class="comments-closed">' . __( 'Apologies, commenting is now disabled for this post.' ) . '</p>';


The comment_form_before hook is fired just before the <div id="respond"> is displayed.


The comment_form_after hook is fired immediately after the closing </div> of <div id="resond">.


This action hook is fired just after the HTML for the must_log_in argument from the arguments listed above. It is only fired if the user is not logged in and comment registration is required for commenting.


This hook is an alternate hook to the previous one. It fires for logged-in users only. It is fired just after the logged_in_as argument from above is displayed.


This hook is fired just after the opening of the <form> element and before any of the form fields are displayed.


This hook is fired just before the closing </form>. It also passes the $post_id variable for use in your function.

You may wonder why it’s not called comment_form_bottom. This is because comment_form is a legacy hook that has been around for ages. It didn’t make much sense to have two hooks that done the same thing, so the developers just stuck with what we already had.

CSS rules

Now that you’ve figured out how to change the markup and know how all the hooks work, you might want to just get back to actually styling your comment form. I won’t go over any specific style stuff here, but I will give you a list of the basic things you can style.

These represent the default HTML output by the comment_form() function. Remember, you have nearly full control over the markup, so any changes you make need to be reflected in your theme’s style.css file.

/* Wrapper div for the entire comment form. */
div#respond { }

/* Reply title. */
h3#reply-title { }

/* Cancel comment link. */
h3#reply-title small { }

/* The comment form. */
form#commentform { }

/* Comment author field. */
p.comment-form-author { }
p.coment-form-author label { }
p.comment-form-author input#author { }

/* Comment email field. */
p.comment-form-email { }
p.comment-form-email label { }
p.comment-form-email input#email { }

/* Comment URL field. */
p.comment-form-url { }
p.comment-form-url label { }
p.comment-form-url input#url { }

/* Required (*) text. */
span.required { }

/* Comment form text. */
p.comment-form-comment { }
p.comment-form-comment label { }
p.comment-form-comment textarea#comment { }

/* Must log in paragraph. */
p.must-log-in { }

/* Logged in paragraph. */
p.logged-in-as { }

/* Comment notes paragraph. */
p.comment-notes { }

/* Allowed tags paragraph. */
p.form-allowed-tags { }
p.form-allowed-tags code { }

/* Paragraph that wraps the submit button and hidden comment ID fields. */
p.form-submit { }

/* Submit button. */
input#submit { }

Give the new comment form a try

I hope you found this tutorial useful and can use the information presented within your own projects. There’s definitely a lot of different ways you can customize the comment form now and plenty of hooks for plugin developers to use for cool plugins.

Please share any awesome customizations you’ve made in the comments. We’d love to hear about them.