How to Create a Simple Post Rating System With WordPress and jQuery
Tutorial Details
- Program: WordPress and jQuery
- Difficulty: Intermediate
- Estimated Completion Time: 30 min
There already are many post rating system plugins out there. Surprisingly, no one fits my needs, they either are too complicated or with too many built-in options. So, in this tutorial, you’ll learn how to build your own simple post rating functionality, directly within your theme files. There’s no need for plugin!
Step 1 Introduction
Post ratings are a good way to get feedback on what your readers really focus on. While I was searching for a simple yet efficient plugin, I realized they all come with too many options and aren’t customizable enough. That was my first issue. My second issue was that using too many plugins will slow WordPress down.
So I decided to share with you a way to build your custom post ratings system. We’ll use:
- simple HTML markup and CSS3 so it’s easy to customize
- jQuery to deal with Ajax calls
- PHP and hooks to handle data
Step 2 HTML Markup
We are going to display a heart-shaped button in the footer section of an article. To do so, we’ll add markup in the content-single.php file (within the Twenty Eleven theme).
<p class="post-like"> <a data-post_id="POST_ID" href="#"> <span class="qtip like" title="I like this article"></span> </a> <span class="count">POST_LIKES_COUNT</span> </p>
Note that we use HTML5 custom meta attributes to store our data so it’s really easy to handle with jQuery. Then, this will be generated by PHP, as we’ll see in a next step.
Step 3 CSS3
We’ll use CSS3 animations to add some visual interactivity and CSS sprites to reduce the number of external images.
article footer .post-like{
margin-top:1em
}
article footer .like{
background:url(images/icons.png) no-repeat;
width: 15px;
height: 16px;
display: block;
float:left;
margin-right: 4px;
-moz-transition: all 0.2s ease-out 0.1s;
-webkit-transition: all 0.2s ease-out 0.1s;
-o-transition: all 0.2s ease-out 0.1s
}
article footer .post-like a:hover .like{
background-position:-16px 0;
}
article footer .voted .like, article footer .post-like.alreadyvoted{
background-position:-32px 0;
}
At this point, it should looks like this

Step 4 jQuery
We’ll use built-in jQuery to handle the ajax requests. On click, jQuery will pass some parameters to our php handler and handle the response to display suitable information.
So let’s create a new file called post-like.js and add it to the js folder. Then open it and add this code:
jQuery(document).ready(function() {
jQuery(".post-like a").click(function(){
heart = jQuery(this);
// Retrieve post ID from data attribute
post_id = heart.data("post_id");
// Ajax call
jQuery.ajax({
type: "post",
url: ajax_var.url,
data: "action=post-like&nonce="+ajax_var.nonce+"&post_like=&post_id="+post_id,
success: function(count){
// If vote successful
if(count != "already")
{
heart.addClass("voted");
heart.siblings(".count").text(count);
}
}
});
return false;
})
})
Explanations
First, let’s retrieve the post ID with jQuery’s data method and then pass it to our PHP handler. The ajax_var variable is created dynamically with PHP (we’ll cover this in the next section).
The best way to use ajax within WordPress is to make calls to admin-ajax.php. It is located in the wp-admin folder and you can bind your callback functions with hooks. Let’s see how it works.
Step 5 PHP and Hooks
How are we going to proceed? We are going to bind some functions to WordPress hooks and then enqueue our script and add some PHP-generated JavaScript variables to be used by jQuery.
Hooks
Let’s open the functions.php file and get started adding our functions.
First things first, we need to bind functions to WordPress hooks. So add these two lines in your file:
add_action('wp_ajax_nopriv_post-like', 'post_like');
add_action('wp_ajax_post-like', 'post_like');
The first hook is fired when a user is logged in and the other when they are not. See more information about how to implement ajax the right way here: 5 tips for using ajax in WordPress.
You can see the “post-like” part of the hook, which is the action parameter we used in the previous step with jQuery.
Now we’ll enqueue our script and declare our variables:
wp_enqueue_script('like_post', get_template_directory_uri().'/js/post-like.js', array('jquery'), '1.0', true );
wp_localize_script('like_post', 'ajax_var', array(
'url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ajax-nonce')
));
Here, we define two important parameters:
- url: the complete URI to admin-ajax.php location
- nonce: a security hash to prevent attacks and mistakes
You can use those parameters with jQuery this way: ajax_var.url and ajax_var.nonce.
“Using WordPress hooks and admin-ajax.php is the safer way to deal with Ajax.”
Functions
Let’s add the post_like function. It will:
- Check nonce
- Retrieve post ID, user’s IP and custom post metas
- Check if the user has already voted or not
- Save data (IP + votes count) for the current post
- Return the count value to jQuery
function post_like()
{
// Check for nonce security
$nonce = $_POST['nonce'];
if ( ! wp_verify_nonce( $nonce, 'ajax-nonce' ) )
die ( 'Busted!');
if(isset($_POST['post_like']))
{
// Retrieve user IP address
$ip = $_SERVER['REMOTE_ADDR'];
$post_id = $_POST['post_id'];
// Get voters'IPs for the current post
$meta_IP = get_post_meta($post_id, "voted_IP");
$voted_IP = $meta_IP[0];
if(!is_array($voted_IP))
$voted_IP = array();
// Get votes count for the current post
$meta_count = get_post_meta($post_id, "votes_count", true);
// Use has already voted ?
if(!hasAlreadyVoted($post_id))
{
$voted_IP[$ip] = time();
// Save IP and increase votes count
update_post_meta($post_id, "voted_IP", $voted_IP);
update_post_meta($post_id, "votes_count", ++$meta_count);
// Display count (ie jQuery return value)
echo $meta_count;
}
else
echo "already";
}
exit;
}
We are saving the user’s IP and current time into one post_meta and votes count into another one. Time will help us to check if user can revote again after a certain elapsed time.
You could ask what happens when no vote has been recorded for the current post yet? Well, WordPress helps us one more time here, because update_post_meta checks if meta exists and creates it if not (see more information about this function on WordPress codex).
We’ll define how long a user has to wait before voting again (in minutes).
$timebeforerevote = 120; // = 2 hours
This variable could be stored in theme admin panel for instance, so it’s easily editable.
Now add this code to check if the user has already voted:
function hasAlreadyVoted($post_id)
{
global $timebeforerevote;
// Retrieve post votes IPs
$meta_IP = get_post_meta($post_id, "voted_IP");
$voted_IP = $meta_IP[0];
if(!is_array($voted_IP))
$voted_IP = array();
// Retrieve current user IP
$ip = $_SERVER['REMOTE_ADDR'];
// If user has already voted
if(in_array($ip, array_keys($voted_IP)))
{
$time = $voted_IP[$ip];
$now = time();
// Compare between current time and vote time
if(round(($now - $time) / 60) > $timebeforerevote)
return false;
return true;
}
return false;
}
“You should always ask yourself if it’s worth using a plugin and slowing down WordPress rather than adding some simple code within your theme.”
Nothing too complicated here, we are just checking if the user has already voted (i.e. his IP address is among votes IP’s list) and if he is allowed to vote again (i.e. elapsed time since he’s voted is greater than $timebeforerevote).
Step 6 Create a Function to Generate the HTML Markup
To get more control over how votes are displayed and make it easy to update if it’s used in different files, we’ll make a function that generates the markup. If the user has already voted, we don’t display the heart as a link to avoid useless ajax calls.
function getPostLikeLink($post_id)
{
$themename = "twentyeleven";
$vote_count = get_post_meta($post_id, "votes_count", true);
$output = '<p class="post-like">';
if(hasAlreadyVoted($post_id))
$output .= ' <span title="'.__('I like this article', $themename).'" class="like alreadyvoted"></span>';
else
$output .= '<a href="#" data-post_id="'.$post_id.'">
<span title="'.__('I like this article', $themename).'"class="qtip like"></span>
</a>';
$output .= '<span class="count">'.$vote_count.'</span></p>';
return $output;
}
You can replace code inserted in Step 2 with this function:
<?php echo getPostLikeLink(get_the_ID());?>
Step 7 Go Further
Now users can vote on posts, you might like to get feedback. For instance, you could display a top 10 of best rated posts.
To do so, you can use query_post with these options:
query_posts('meta_key=votes_count&orderby=meta_value_num&order=DESC&posts_per_page=10');
Step 7 Conclusion
That’s it! You should now be able to track your posts according to users’ votes and customize both the markup and the CSS to your convenience. This is just one way to create a post rating system, among others, so don’t hesitate to comment on this tutorial and give your feedback. Thank you for reading!

I haven’t looked through the code in a proper manner yet, but do you think it would be easy to customize this code to be a 5 star rating instead?
It seems to be really hard to find an easy solution for a star rating, that isn’t a complete overkill.
Yes UUgee, it won’t be too complicated, you’ll just have to replace the heart shaped button with a 5 stars rating plugin, then edit the php code to save and return the average value for the post and eventually change the view to display average value with the jQuery plugin.
Please do share a Star solution as well as this nice Like tutorial.
I commented below but yeah, would be good, especially for a review type site so you can add Google Rich Snippets that will show the rating in a Google search.
I would really like to see a star rating based on this on wp.tuts, it would be awsome! And not a complicated plugin, just an easy way to get the ratings as custom fields, where the developer then could continue and show them as he wishes.
Allright ! I’ll try to take some time to provide a 5-star system ! It could indeed be very nice if it meets Google search requirements !
Here are the quick steps to follow to add a 5-star system.
I downloaded and enqueued the great jQuery Star Rating Plugin.
Then I edited the post_like function like so :
function post_like() { ... if(isset($_POST['post_rate'])) { // Retrieve user IP address $ip = $_SERVER['REMOTE_ADDR']; $post_id = $_POST['post_id']; $vote_value = $_POST['post_rate']; // Get voters'IPs for the current post $meta_IP = get_post_meta($post_id, "voted_IP"); $voted_IP = $meta_IP[0]; if(!is_array($voted_IP)) $voted_IP = array(); // Get votes count for the current post $meta_count = get_post_meta($post_id, "votes_count", true); $meta_votes_values = get_post_meta($post_id, "votes_values", true); $meta_votes_values[] = $vote_value; // Use has already voted ? if(!hasAlreadyVoted($post_id)) { $voted_IP[$ip] = time(); // Save IP and increase votes count update_post_meta($post_id, "voted_IP", $voted_IP); update_post_meta($post_id, "votes_count", ++$meta_count); update_post_meta($post_id, "votes_values", $meta_votes_values); // Display average rating value echo round(array_sum($meta_votes_values) / $meta_count); } else echo "already"; } exit; }I added a third post_meta information to store all the votes values and return an average value.
Then in the function that generates the markup, I replaced the heart shaped button with the 5-star system :
function getPostLikeLink($post_id) { $themename = "twentyeleven"; $vote_count = get_post_meta($post_id, "votes_count", true); $meta_votes_values = get_post_meta($post_id, "votes_values", true); if(is_array($meta_votes_values)) $avg = round(array_sum($meta_votes_values) / $vote_count); else $avg = 0; $output = ' '; for($i=1; $i<=5; $i++) { $output .= ''; } $output .= ''; return $output; }I get all values from post_meta and use the average value to get the good radio button checked.
Eventually, I changed the JS file :
jQuery(".post-like input").rating({ callback: function(value, link){ postID = $(link).parents(".post-like").data("post_id"); jQuery.ajax({ type: "post", url: ajax_var.url, data: "action=post-like&nonce="+ajax_var.nonce+"&post_rate="+value+"&post_id="+postID, success: function(){ // Disable vote // Set the average value // ... } }); } });It uses the jquery plugin to submit via ajax the value to be stored in post metas.
And that’s it !
It should be compliant with Google rating system. (see here for more information).
Hope it helps !
Thank you for providing a start! This isn’t as far developed as the original article, at least looks that way. Did you get this to fully function?
Really nice of you to provide this much help to the community, envanto should pay you double for this article
Thanks for the tuto!!! I had no problem getting work the first part but since I started I was missing the five star system..It was a great surprise to find it on commnets but I can do it work..
I followed the steps:
1. Enqueue the five stars scripts
wp_enqueue_script('five-stars-rates', get_template_directory_uri().'/js/five-stars-rates.js', array('jquery'), '1.0', true );2. Edit post_like function with that result:
function post_like() { // Check for nonce security $nonce = $_POST['nonce']; if ( ! wp_verify_nonce( $nonce, 'ajax-nonce' ) ) die ( 'Vetarcarahoo!'); if(isset($_POST['post_like'])) { // Retrieve user IP address $ip = $_SERVER['REMOTE_ADDR']; $post_id = $_POST['post_id']; // Get voters'IPs for the current post $meta_IP = get_post_meta($post_id, "voted_IP"); $voted_IP = $meta_IP[0]; if(!is_array($voted_IP)) $voted_IP = array(); // Get votes count for the current post $meta_count = get_post_meta($post_id, "votes_count", true); // Use has already voted ? if(!hasAlreadyVoted($post_id)) { $voted_IP[$ip] = time(); // Save IP and increase votes count update_post_meta($post_id, "voted_IP", $voted_IP); update_post_meta($post_id, "votes_count", ++$meta_count); // Display count (ie jQuery return value) echo $meta_count; } else echo "ya votaste!"; } exit; if(isset($_POST['post_rate'])) { // Retrieve user IP address $ip = $_SERVER['REMOTE_ADDR']; $post_id = $_POST['post_id']; $vote_value = $_POST['post_rate']; // Get voters'IPs for the current post $meta_IP = get_post_meta($post_id, "voted_IP"); $voted_IP = $meta_IP[0]; if(!is_array($voted_IP)) $voted_IP = array(); // Get votes count for the current post $meta_count = get_post_meta($post_id, "votes_count", true); $meta_votes_values = get_post_meta($post_id, "votes_values", true); $meta_votes_values[] = $vote_value; // Use has already voted ? if(!hasAlreadyVoted($post_id)) { $voted_IP[$ip] = time(); // Save IP and increase votes count update_post_meta($post_id, "voted_IP", $voted_IP); update_post_meta($post_id, "votes_count", ++$meta_count); update_post_meta($post_id, "votes_values", $meta_votes_values); // Display average rating value echo round(array_sum($meta_votes_values) / $meta_count); } else echo "already"; } exit; }2. Edit getPostLikeLink function with that result:
function getPostLike($post_id) { $themename = "flamenco"; $vote_count = get_post_meta($post_id, "votes_count", true); $meta_votes_values = get_post_meta($post_id, "votes_values", true); if(is_array($meta_votes_values)) $avg = round(array_sum($meta_votes_values) / $vote_count); else $avg = 0; $output = ' '; for($i=1; $i<=5; $i++) { $output .= ''; } $output .= ''; return $output; }4. and finally edit my JS file with that result:
jQuery(".post-like input").rating({ callback: function(value, link){ postID = $(link).parents(".post-like").data("post_id"); jQuery.ajax({ type: "post", url: ajax_var.url, data: "action=post-like&nonce="+ajax_var.nonce+"&post_rate="+value+"&post_id="+postID, success: function(){ // Disable vote // Set the average value // ... } }); } });Please some help…I want my five stars hotel
Thanks!!
Having trouble getting this to work. In ‘getPostLikeLink’ I was able to check for the value of $avg and echo out the appropriate input tags based on that value. Everything displays correctly, but for whatever reason the data is not getting saved to the custom field. The heart version worked perfectly, but as soon as I augmented that code with what you have above, nothing gets saved when I click on the carting. Please advise. Thanks!
Hi Guillaume
Thanks for the great tutorial. Getting back on the five star rating update you have posted, it seems as the getPostLikeLink function hasn’t copied over fully! Looks as if there is some code missing!?
Would you mind reposting the full function? Thanks a lot and kind regards in advance!
Great stuff. I was just about to tackle this myself and I’ve spent some time scouring Google for a solution to this but all I could find was super duper plugin this and 2million options plugin that. Thanks
wow i was looking something like this days ago and here it is, thanks for the great article Guillaume
This is a great little feature and a good tutorial, so it is a shame that you mislead people by claiming that adding a plugin is “slowing down WordPress” – there is no difference between putting code in a plugin and putting the same code into your theme file. Plugins are simply files that WordPress includes. A few lines of badly written code in a theme file can slow WordPress down much more than 10 well-written plugins.
Using plugins is very good practice, not the negative thing you portray it to be: updating your code or using it across different websites becomes much easier, and you give end users the option to easily turn something on or off with one click.
I didn’t mean using plugin is bad, It’s one of the coolest WordPress’ feature. I just pointed out that is you use this code as a plugin, you’ll have to enqueue separate javascript, stylesheets and images whereas you could simply add this to your themes’s template files. This will save you external server requests so it won’t reduce page load.
You could totally make a plugin out of it, the question is, is it really worth it ?
the advantage of using a plugin is that if the user changes the theme, then they’ll still see the post ratings
Oh man, I’ve got to try this out!
Wow, didn’t know about the post_meta() shortcuts, I’ve always used raw MySQL queries.
Thanks Guillaume!
The post meta gets stored in the ‘wp_postmeta’, will there be any issues when changing themes? (I guess you will need to code as a plugin or copy-paste into the functions.php).
Also, what if I delete a post? Does the post meta get cleaned or will I end up with loads of old data in the DB?
Awesome one as always thanks.
Wow. This came in just in time. I’ve been working on a similar implementation for the last couple of days. Good job.
I have question. IF same code is converted to plugin. Is that become performance issue. As plugin and theme run same code. So how the performance issue. which you told.
“You should always ask yourself if it’s worth using a plugin and slowing down WordPress rather than adding some simple code within your theme.”
Please a clear idea how it happen.
See my answer to Simon
Thanks! I guess to enhance it someone could change the rating system to a 5-star type system so that you could change it for Google Rich Snippets in order for the rating to show up in Google’s search.
I really like the idea of not bloating things with a plug-in but the problem is then having to hard-code the getPostLikeLink(get_the_ID()) function into my theme.
In Step2 you say “add markup in the content-single.php file (within the Twenty Eleven theme).” but I’m using a child theme (Graphene with slightly altered CSS) and I don’t want to alter the parent theme’s php code as this would make updates difficult to implement (i.e. having to remember to add the custom code every time)
Is there a way around this?
In an attempt to answer my own question, I’ve been finding out about WordPress Action Hooks (http://www.nathanrice.net/blog/an-introduction-to-wordpress-action-hooks/)
I’ve found that Graphene provides a number of these:
http://www.khairul-syahir.com/wordpress-dev/graphene-theme/graphene-action-hooks
So I’ve tried adding the following at the end of function.php
function insert_postlike_code() {
echo ‘getPostLikeLink($post_id);’;
}
add_filter(‘graphene_after_post_content’,'insert_postlike_code’);
But what gets echoed out at the bottom of each post is just the string literal, i.e. “getPostLikeLink($post_id);”
If I change my function to
function insert_postlike_code() {
echo ‘eval(“getPostLikeLink($post_id);”);’;
}
then the same, just the string “eval(“getPostLikeLink($post_id);”);” appears.
Finally in desperation I tried printing the code from Step 2, and tried:
function insert_postlike_code() {
echo ‘<p class=”post-like”>’.”\n”;
echo ‘ <a data-post_id=”POST_ID” href=”#”> ‘.”\n”;
echo ‘ <span class=”qtip like” title=”I like this article”></span> ‘.”\n”;
echo ‘ </a> ‘.”\n”;
echo ‘ <span class=”count”>POST_LIKES_COUNT</span> ‘.”\n”;
echo ‘</p> ‘.”\n”;
}
add_filter(‘graphene_after_post_content’,'insert_postlike_code’);
But that just displays “POST_LIKES_COUNT”, although when I view source I see that the HTML is:
<p class=”post-like”>
<a data-post_id=”POST_ID” href=”#”>
<span class=”qtip like” title=”I like this article”></span>
</a>
<span class=”count”>POST_LIKES_COUNT</span>
</p>
I’ve check that post-like.js is indeed being loaded, so that’s not the problem… Anyone have any ideas how I can get this to work?
Yeah ! Just what I needed ! Thank you so much
Hi Guillaume,
Thanks for the great post. I have used a modified version of the plugin http://wordpress.org/extend/plugins/i-like-this/ on my personal website http://www.harishchouhan.com.
I have been also in the process of submitting my version on WordPress, but looking at your code found it lot simpler.
Can you please share with me your email address as I need to ask you about few things.
guillaumevoisin at gmail
is there any way to build a post rating system like this and make most voted posts go to top page?
Yes you just have to insert code at step 7 into the page template or wherever you want, or use it within a shortcode function [top_posts nb=7 ... ].
That’s what I’m doing with my own theme
Great article, just what I’ve been looking for & none of the plugins out there seemed to offer the right simplicity.
However, I’ve tried it out on my localhost and it doesn’t seem to be working for me. I can click the hearts and it changes the vote count from 0 but replaces it with the HTML of the front page. When I refresh this page, it has incremented the count to one as it should. If I login to WordPress, it works fine, it increments the post counts as it should.
In both situations, it lets me vote more than once on each post after the 60 seconds have elapsed.
I’ve gone over the instructions twice, and simply copied & pasted your code into my own theme and it doesn’t work. Anybody else had a similar experience or am I missing something.
Thanks!
Hi,
I’ve tried it in the Twentyeleven theme that comes with WordPress, and this time there’s no issue with the HTML source of the index page, when I’m logged out the count increments as it should but it still doesn’t limit me from voting more than once on the same article nor does it display any message after I’ve voted.
I will persevere and get to the bottom of this, even if it does take me all day
John
Hey John,
Is the $timebeforerevote variable still set to 120 seconds ? If it is, you may have to debug this with Firebug, just open the ajax request in a new tab and see what it says. You can print IPs array as well as your own, vote count etc…
I’m sure you’ll figure it out. Don’t hesitate to tell us if you managed to get it to work or not.
Hi Guillaume,
Awesome post. I’ve been looking for a non-plugin solution to this as well. My question is would it be possible to have this translate into a rating system for photos in post instead of the post itself? Would I just be replacing POST_ID with an attachment ID? Any help with this would be awesome. Thanks!
-Mario
Hi Mario,
Actually, it works already ! As photos behave like posts, you can already vote for photos. If you like to restrict vote to attachments only, you could add a check in the vote function to be sure the post is an attachment. Otherwise, you’ve got nothing to do
Wow. This came in just in time. I’ve been working on a similar implementation for the last couple of days. Good job. Thanks tutsplus.
This is a great tutorial. I will definitely use this some time.
I do have a question: Is there a way to completely remove the $timebeforerevote so that the user can only ever vote once?
Well, you just have to edit the if statement to only check IP address has already been saved so user can not vote anymore. That should do the trick.
This is great!!!
I dint know about wp_localize_script() and wp_ajax Hooks, very helpful.
Thank You =)
Could you also add Dislike, too? Can’t find a good plugin to do both Like and Dislike anywhere!
Any way to show the favorites post from a loged user??