
If you have a WordPress site with a bbPress Forum and you’re looking to add user voting functionality, then you’ve come to the right place.
As an experienced WordPress developer and the developer of the free bbPress Voting plugin, I have extensive experience with this that I’m happy to share with you.
In this article, I will be sharing with you lots of code snippets that you can use to implement voting on your bbPress forum. However, you might find it a lot more simple and it would include a lot more features if you just install my free plugin.
But, if you’re the type who likes to code it yourself, or you just want to learn how to do it to get better at WordPress development, then read on.
Adding the Voting Up & Down Buttons
In this code snippet, we are hooking into 3 different action hooks that are exposed by bbPress and we are running the same function for all them which will handle adding the voting buttons.
In that function, first it uses both the current post_type and the current_action() (to know which hook is being used) to get the post data.
After we get the post data for that topic or reply, we get some post meta out of it which we will be using to store the ups, downs, score, and voting log.
Voting tracking uses the logged in user ID or the IP address of a not-logged-in visitor.
With all of that, we build the HTML that will be echoed at the end of the function.
add_action('bbp_theme_before_topic_title', 'wpftw_bbp_voting_buttons');
add_action('bbp_theme_before_topic_content', 'wpftw_bbp_voting_buttons');
add_action('bbp_theme_before_reply_content', 'wpftw_bbp_voting_buttons');
function wpftw_bbp_voting_buttons() {
$topic_post_type = bbp_get_topic_post_type();
$reply_post_type = bbp_get_reply_post_type();
if(current_action() === 'bbp_theme_before_topic_title' || current_action() == 'bbp_theme_before_topic_content') {
// Topic hook will always be the topic
$this_post_type = $topic_post_type;
}
if(current_action() === 'bbp_theme_before_reply_content') {
// Reply hook could be topic (OP) or a reply
$this_post_type = bbp_voting_get_current_post_type();
}
// Get the post
if($this_post_type == $topic_post_type) $post = bbpress()->topic_query->post;
if($this_post_type == $reply_post_type) $post = bbpress()->reply_query->post;
// Do we have a post?
if(!empty($post)) {
$post_id = $post->ID;
$score = (int) get_post_meta($post_id, 'bbp_voting_score', true);
$ups = (int) get_post_meta($post_id, 'bbp_voting_ups', true);
$downs = (int) get_post_meta($post_id, 'bbp_voting_downs', true);
// Check for, and correct, discrepancies
$calc_score = $ups + $downs;
if($score > $calc_score) {
$diff = $score - $calc_score;
$ups += $diff;
update_post_meta($post_id, 'bbp_voting_ups', $ups);
}
// Get user's vote by ID or IP
$voting_log = get_post_meta($post_id, 'bbp_voting_log', true);
$voting_log = is_array($voting_log) ? $voting_log : array(); // Set up new array
$client_ip = $_SERVER['REMOTE_ADDR'];
$identifier = is_user_logged_in() ? get_current_user_id() : $client_ip;
$existing_vote = array_key_exists($identifier, $voting_log) ? $voting_log[$identifier] : 0;
// Start HTML
$html = '';
$html .= '<div class="wpftw-bbp-voting wpftw-bbp-voting-post-'.$post_id. ($existing_vote == 1 ? ' voted-up' : ($existing_vote == -1 ? ' voted-down' : '')) .'">';
// Up vote
$html .= '<a class="vote up" data-votes="'.($ups ? '+'.$ups : '').'" onclick="wpftw_bbpress_post_vote_link_clicked(' . $post_id . ', 1); return false;">Up</a>';
// Display current vote count for post
$html .= '<div class="score">'. $score. '</div>';
// Down vote
$html .= '<a class="vote down" data-votes="'.($downs ? $downs : '').'" onclick="wpftw_bbpress_post_vote_link_clicked(' . $post_id . ', -1); return false;">Down</a>';
$html .= '</div>';
echo $html;
}
}
Style the Voting Buttons with SCSS
With this SCSS (Sass) which you can compile down to plain CSS will handle the styling of the voting buttons. It makes the up and down buttons be arrows with green and red hover colors, and it has the score in a large font in the middle. It looks just like what you would find on Stack Overflow forums.
$green: green;
$red: #a11717;
.wpftw-bbp-voting {
margin: 8px 0;
padding-right: 30px;
min-width: 65px;
.score {
font-size: 25px;
font-weight: bold;
text-align: center;
color: #858c93;
padding: 2px 0 3px 0;
}
a.vote {
display: block !important;
position: relative;
overflow: visible;
text-indent: -9999em;
width: 0;
height: 0;
margin: 3px auto !important;
border: inherit !important;
border-left: 15px solid transparent !important;
border-right: 15px solid transparent !important;
cursor: pointer;
&.up {
border-bottom: 15px solid #858c93 !important;
&:hover {
border-bottom: 15px solid #444 !important;
}
}
&.down {
border-top: 15px solid #858c93 !important;
&:hover {
border-top: 15px solid #444 !important;
}
}
&:after {
content: attr(data-votes);
display: block;
position: absolute;
top: -10px;
text-align: left;
margin-left: 17px;
text-indent: 0;
color: #aaa;
opacity: 0;
transition: opacity 500ms ease;
}
}
&.voted-up a.vote.up {
border-bottom-color: $green !important;
}
&.voted-down a.vote.down {
border-top-color: $red !important;
}
}
ul.bbp-topics {
.wpftw-bbp-voting {
float: left;
}
}
AJAX to Handle Vote Clicks
Now that we have buttons and styled them, we need to make the buttons do something. We need to add an AJAX handler using the standard WordPress way of doing it.
Add a function to hook into wp_enqueue_scripts and in there, you will enqueue a javascript file and also use wp_localize_script to make it accessible via AJAX through WordPress.
add_action('wp_enqueue_scripts', 'wpftw_bbp_voting_scripts');
function wpftw_bbp_voting_scripts() {
wp_enqueue_script( 'wpftw-bbp-voting-js', plugin_dir_url(__FILE__) . '/wpftw-bbp-voting.js', array('jquery'), filemtime(plugin_dir_path(__FILE__). 'wpftw-bbp-voting.js') );
wp_localize_script( 'wpftw-bbp-voting-js', 'wpftw_bbp_voting_ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
The content of that javascript file should look something like this below. It handles that javascript that executes when you click a vote button, sends the data via AJAX and handles the response from the server.
function wpftw_bbpress_post_vote_link_clicked(post_id, direction) {
var post_clicked = jQuery('.wpftw-bbp-voting.wpftw-bbp-voting-post-'+post_id);
// Loading CSS
post_clicked.css('opacity', 0.5).css('pointer-events', 'none');
// Ajax data
var data = {
'action': 'wpftw_bbpress_post_vote_link_clicked',
'post_id': post_id,
'direction': direction
};
jQuery.post(wpftw_bbp_voting_ajax_object.ajax_url, data, function(response) {
if(response.hasOwnProperty('error')) {
// Error response
console.log('Voting error:', response.error);
} else if(!response.hasOwnProperty('score')) {
// Catch invalid AJAX response
console.log('SOMETHING WENT WRONG', response);
} else {
// Proper response that has score, direction, ups, and downs
var score = parseInt(response.score);
direction = parseInt(response.direction);
var up = parseInt(response.ups);
var down = parseInt(response.downs);
console.log('Voted ' + direction, 'post #' + post_id, 'score: ' + score, 'ups: ' + up, 'downs: ' + down);
jQuery('.wpftw-bbp-voting.wpftw-bbp-voting-post-'+post_id).each(function() {
// Get elements
var wrapper = jQuery(this);
var score_el = jQuery(this).find('.score');
var up_el = jQuery(this).find('.up');
var down_el = jQuery(this).find('.down');
// Set elements' html
score_el.html(score);
up_el.attr('data-votes', '+' + up);
down_el.attr('data-votes', down);
// Change arrow colors
if(direction > 0) {
// Up vote
up_el.css('border-bottom-color', '#1e851e');
wrapper.removeClass('voted-down').addClass('voted-up');
} else if (direction < 0) {
// Down vote
down_el.css('border-top-color', '#992121');
wrapper.removeClass('voted-up').addClass('voted-down');
} else if (direction == 0) {
// Remove vote
up_el.css('border-bottom-color', 'inherit');
down_el.css('border-top-color', 'inherit');
wrapper.removeClass('voted-down').removeClass('voted-up');
}
// Restore the CSS
wrapper.css('opacity', 1).css('pointer-events', 'auto');
});
}
});
}
Server-side API to Receive the Vote Click AJAX Call
Now, we need to write the server-side PHP code that is executed by that AJAX call. This takes care of actually updating the post meta values in the database and responding to the AJAX call.
// Process a vote
add_action('wp_ajax_bbpress_post_vote_link_clicked','wpftw_bbpress_post_add_vote');
add_action('wp_ajax_nopriv_bbpress_post_vote_link_clicked','wpftw_bbpress_post_add_vote');
function wpftw_bbpress_post_add_vote(){
header('Content-type: application/json');
$post_id = (int) $_POST['post_id'];
// Direction
$direction = (int) $_POST['direction'];
$direction = in_array($direction, [1, -1]) ? $direction : 0; // Enforce 1 or -1
$voting_log = get_post_meta($post_id, 'bbp_voting_log', true);
$voting_log = is_array($voting_log) ? $voting_log : array(); // Set up new array
$client_ip = $_SERVER['REMOTE_ADDR'];
$identifier = is_user_logged_in() ? get_current_user_id() : $client_ip;
$remove_vote = false;
$reverse_vote = false;
// Catch user voted already (by user ID or IP)
if(array_key_exists($identifier, $voting_log)) {
// Identifier found
if($voting_log[$identifier] == $direction) {
// Voting again in the same direction
$remove_vote = true;
} elseif($voting_log[$identifier] == $direction * -1 ) {
// Changing the vote in different direction
$reverse_vote = true;
} else {
// Changing vote from 0
}
}
// All good, add the user's vote
// But first get all the data
$score = (int) get_post_meta($post_id, 'bbp_voting_score', true);
$ups = $ups_og = (int) get_post_meta($post_id, 'bbp_voting_ups', true);
$downs = $downs_og = (int) get_post_meta($post_id, 'bbp_voting_downs', true);
if($direction > 0) {
// Up vote
if($remove_vote) {
$ups = $ups - 1;
$score = $score - 1;
} else {
$ups = $ups + 1;
$score = $score + 1;
}
if($reverse_vote) {
$downs = $downs + 1;
$score = $score + 1;
}
} elseif($direction < 0) {
// Down vote
if($remove_vote) {
$downs = $downs + 1;
$score = $score + 1;
} else {
$downs = $downs - 1;
$score = $score - 1;
}
if($reverse_vote) {
$ups = $ups - 1;
$score = $score - 1;
}
}
// Update the score
update_post_meta($post_id, 'bbp_voting_score', $score);
// Update the ups and downs if needed
if($ups !== $ups_og) update_post_meta($post_id, 'bbp_voting_ups', $ups);
if($downs !== $downs_og) update_post_meta($post_id, 'bbp_voting_downs', $downs);
// Log the user's ID or IP
$real_direction = $remove_vote ? 0 : $direction;
$voting_log[$identifier] = $real_direction;
update_post_meta($post_id, 'bbp_voting_log', $voting_log);
echo json_encode(array(
'score' => $score,
'direction' => $real_direction,
'ups' => $ups,
'downs' => $downs
));
exit;
}
There’s a Better Way
Again, I share all these details and code snippets with you for your learning, but if you want all this and much more features packed into it, just download my free bbPress Voting plugin from the WordPress Plugin Directory.
I hope you enjoy it!
If you have any questions about any of this code, please comment below. If you need support for the plugin, please use the Support tab on the plugin page at wordpress.org. Thanks!
It Gets EVEN BETTER!
This post went over pretty basic functionality of adding voting buttons to bbPress, and you can just download my free plugin to get that with more features built in. But, did you know you can get a whole lot more features?
Let me introduce you to my bbPress Voting Pro plugin which has all the bells and whistles!
Pro Features Available:
- Accepted answers
- “Who voted” avatars
- Sort dropdown
- Sort on weighted score
- Trending topics widget
- Schema for Q&A rich snippets
- Voting email notification to author
- Mark topics or replies with poor scores as spam
Get the bbPress Voting Pro plugin and take your bbPress forum to the next level!

Blogger, expert WordPress developer, and developer of the awesome bbPress Voting plugin which is a must-have plugin for any bbPress forum.
Download bbPress Voting for free on the WordPress Plugin Directory.