There are a lot of commenting system plugins out there but most of them come with a cost, and sometimes just building a simple WordPress commenting system on top of the native commenting system can be enough.

The main features of this system will be:

  • Comments will be only loaded if the user clicks a button
  • Comments will be submitted and fetched by JS
  • Comments will have paginations fetched by JS
  • Comments will have some JS interactivity
  • Comments will have the reply feature enabled
  • The comment system will have an AMP implementation
  • The comment system will have a no JS implementation
  • Comments will work the same on AMP with our own script

The first thing will have to do for our system is to make some custom REST API for the JS to fetch.

We can add the following code to functions.php:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

We added 2 REST routes and modified the default route for posting comments.

For the modified route of submitting comments will use the default WordPress with some simple modifications, one is that we will return the avatar image, and the second is that we will make sure that the file returns JSON instead of a redirect.

The custom posting comment file comment-post.php:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

The original WordPress code is commented.

Then wee need to make sure we load are JS for the regular version and for the AMP version.

So in functions.php we need to have something like this:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

As you see on the non-AMP mode we will make use of the default comment-reply.js and on AMP we will implement that functionality in app_comments.js.

Note that comment-reply.js does not need JQuery, also I would recommend altogether dumping JQuery on the frontEnd if you use WP 5.7 or later since is not needed by WP-core only by some plugins and there are many plugins that do not need JQuery.

After that, we should modify the comments.php file which is used for rendering the comments on the page, here is the full content of comments.php:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

Mostly for the AMP version we need to include the amp-script element that will encompass the comments section, if we use the official amp-script plugin we don't need to include the dependency for amp-script because it's automatically included.

In the above code to bypass the automatic XHRequest that is automatically made by the amp-form, we simply insert the post button outside the from, but in case there is no JavaScript will still display the normal button so comments could be summited without JavaScript, though in that case, we should also modify comment-post.php, to detect if the request is made for JavaScript or not, in other words, we need to write some more code to fully support a non-JavaScript system.

Also as a side node to hide elements in amp when JS is not enabled can be done with:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

This works because AMP will automatically append the amp-version attribute, and if no JS is available the attribute will not be there.

Also in the comments.php, we should have a way to include our amp-script that handles the comments, in the above code is done with src="/js/AMP/amp_comments.js".

I won't put the CSS here but I'll provide the link to the repo with all the code.

So to finalize our system we should put our two JS scripts one for AMP(which will run in the worker-dom) and one for NON-AMP.

JS for NON-AMP:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

JS for AMP:

<?php
//...

add_action('rest_api_init', 'change_rest_post' );
function change_rest_post(){

  register_rest_route( 'a309/v1', '/get-comments-no/post/(?P&lt;post_id>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_top_level_comments_number',
    'permission_callback' => '__return_true',
  ) );
    
   register_rest_route( 'a309/v1', '/get-comments/post/(?P&lt;post_id>\d+)/page/(?P&lt;page_no>\d+)', array(
    'methods' => 'GET',
    'callback' => 'get_comments_post',
    'permission_callback' => '__return_true',
  ) );
 
}

function get_top_level_comments_number( $data ) {

    global $wpdb;
    $data['post_id'] = esc_sql($data['post_id']);
    $noComments = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = '".$data['post_id']."'" );
    wp_send_json([ 'commentNumber' => $noComments ]);
}

function get_comments_post($data){

// setup a fake POST to trick wp_list_comments
global $post; 
$post = new stdClass();

$post->ID = $data['post_id'];
setup_postdata( $post );

$comment_args = array(
					'avatar_size' => 60,
                                        'reverse_top_level' => true,
                                        'reverse_children' => true,
					'style'       => 'ol',
					'short_ping'  => true,
                                        'max_depth' => 5,
                                        'per_page' => 5,
                                        'page' =>    $data['page_no'],
                                        'echo' => false,
                                       
				);

$template = wp_list_comments( $comment_args );
		 
 wp_reset_postdata();
 
      wp_send_json([ 'comments' => $template, 'post_id' => $data['post_id'], 'page_no' => $data['page_no'] ]);
}


// Ajax Comment

function change_comment_action_url( $defaults ) {
	$defaults['action'] = get_template_directory_uri().'/inc/comment-post.php';
	return $defaults;
}

add_filter( 'comment_form_defaults', 'change_comment_action_url');

//...

And that's should do it, here's the repo link: https://gitlab.flashsoft.eu/andrei0x309/tailwindcss-wordpress-theme.

And also here is a demo GIF of the WordPress commenting system:

Wordpress commenting system