Estimated Reading Time: 8 min
Below is a basic approach for adding like / unlike functionality to WordPress posts, limited by visitor IP. When a visitor clicks “like” or “unlike,” the script will:
- Check if that visitor’s IP has previously liked/unliked the post.
- If not, it updates the stored count and logs that IP, preventing multiple votes from the same IP.
This is a simplified version for demonstration. In production, you may want to consider stronger methods (user accounts or cookies, rate-limiting, etc.).

1. Basic Setup
We’ll place everything in a custom plugin for easy maintenance. Create a folder named wp-like-unlike-ip in wp-content/plugins/, and inside it, create a file wp-like-unlike-ip.php.
<?php
/**
* Plugin Name: WP Like/Unlike by IP
* Description: Adds basic like/unlike functionality to WordPress posts, tracking votes by IP.
* Version: 1.0
* Author: Tokyo Blade
* License: GPL2
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Enqueue JavaScript for handling AJAX like/unlike actions.
*/
function wpluip_enqueue_scripts() {
// Only enqueue on single posts (or wherever you want the buttons)
if ( is_single() ) {
wp_enqueue_script(
'wpluip-like-unlike',
plugin_dir_url( __FILE__ ) . 'js/wpluip-like-unlike.js',
array( 'jquery' ),
'1.0',
true
);
// Localize script with AJAX URL and nonce
wp_localize_script( 'wpluip-like-unlike', 'wpluip_ajax_obj', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wpluip_ajax_nonce' ),
) );
}
}
add_action( 'wp_enqueue_scripts', 'wpluip_enqueue_scripts' );
/**
* Register AJAX actions for logged-in and non-logged-in users.
*/
add_action( 'wp_ajax_wpluip_like_unlike', 'wpluip_like_unlike_callback' );
add_action( 'wp_ajax_nopriv_wpluip_like_unlike', 'wpluip_like_unlike_callback' );
/**
* Handle the like/unlike AJAX request.
*/
function wpluip_like_unlike_callback() {
// Check nonce
check_ajax_referer( 'wpluip_ajax_nonce', 'security' );
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
$action_type = isset( $_POST['action_type'] ) ? sanitize_text_field( $_POST['action_type'] ) : '';
if ( ! $post_id || ! in_array( $action_type, array( 'like', 'unlike' ), true ) ) {
wp_send_json_error( array( 'message' => 'Invalid parameters.' ) );
}
// Get user IP
$user_ip = wpluip_get_user_ip();
// Store IPs in post meta: wpluip_like_ips, wpluip_unlike_ips
$like_ips = get_post_meta( $post_id, 'wpluip_like_ips', true );
$unlike_ips = get_post_meta( $post_id, 'wpluip_unlike_ips', true );
if ( ! is_array( $like_ips ) ) {
$like_ips = array();
}
if ( ! is_array( $unlike_ips ) ) {
$unlike_ips = array();
}
// Check if this IP has already voted in the same category
if ( $action_type === 'like' && in_array( $user_ip, $like_ips, true ) ) {
wp_send_json_error( array( 'message' => 'You already liked this post.' ) );
}
if ( $action_type === 'unlike' && in_array( $user_ip, $unlike_ips, true ) ) {
wp_send_json_error( array( 'message' => 'You already unliked this post.' ) );
}
// Remove this IP from the opposite list if it exists (switching vote)
if ( $action_type === 'like' && in_array( $user_ip, $unlike_ips, true ) ) {
$unlike_ips = array_diff( $unlike_ips, array( $user_ip ) );
}
if ( $action_type === 'unlike' && in_array( $user_ip, $like_ips, true ) ) {
$like_ips = array_diff( $like_ips, array( $user_ip ) );
}
// Update IP list accordingly
if ( $action_type === 'like' ) {
$like_ips[] = $user_ip;
} else { // 'unlike'
$unlike_ips[] = $user_ip;
}
update_post_meta( $post_id, 'wpluip_like_ips', $like_ips );
update_post_meta( $post_id, 'wpluip_unlike_ips', $unlike_ips );
// Update counters
$like_count = count( $like_ips );
$unlike_count = count( $unlike_ips );
// Send back updated data
wp_send_json_success( array(
'message' => 'Vote recorded',
'like_count' => $like_count,
'unlike_count' => $unlike_count,
) );
}
/**
* Utility function to get user IP.
*/
function wpluip_get_user_ip() {
// Basic approach (there are many ways to get IP, see Note below)
if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
return sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
return sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
} else {
return sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
}
}
/**
* Shortcode to display Like/Unlike buttons + counts.
* Usage: [wpluip_like_unlike post_id="123"]
* If post_id is omitted, it uses current post in a loop.
*/
function wpluip_like_unlike_shortcode( $atts ) {
global $post;
$atts = shortcode_atts(
array(
'post_id' => 0,
),
$atts,
'wpluip_like_unlike'
);
$post_id = absint( $atts['post_id'] );
// If no post_id in shortcode, use the global $post if available
if ( ! $post_id && $post ) {
$post_id = $post->ID;
}
if ( ! $post_id ) {
return '<p>No valid post ID specified.</p>';
}
// Get current counts
$like_ips = get_post_meta( $post_id, 'wpluip_like_ips', true );
$unlike_ips = get_post_meta( $post_id, 'wpluip_unlike_ips', true );
$like_count = is_array( $like_ips ) ? count( $like_ips ) : 0;
$unlike_count = is_array( $unlike_ips ) ? count( $unlike_ips ) : 0;
ob_start();
?>
<div class="wpluip-vote-container" data-post-id="<?php echo esc_attr( $post_id ); ?>">
<button type="button" class="wpluip-like-button">
Like (<span class="wpluip-like-count"><?php echo esc_html( $like_count ); ?></span>)
</button>
<button type="button" class="wpluip-unlike-button">
Unlike (<span class="wpluip-unlike-count"><?php echo esc_html( $unlike_count ); ?></span>)
</button>
</div>
<?php
return ob_get_clean();
}
add_shortcode( 'wpluip_like_unlike', 'wpluip_like_unlike_shortcode' );
Notes on Getting IP Address
Getting the “real” user IP can be complicated if proxies or load balancers are involved. The above method is basic. If you need a more robust approach, use services like Cloudflare’s CF-Connecting-IP header or specialized libraries that parse various X-Forwarded-For headers.

2. Create the JavaScript File
Inside wp-like-unlike-ip/js/ create a file named wpluip-like-unlike.js:
jQuery(document).ready(function($) {
// Handle Like Button Click
$(document).on('click', '.wpluip-like-button', function() {
const container = $(this).closest('.wpluip-vote-container');
const postId = container.data('post-id');
// Prepare AJAX data
const data = {
action: 'wpluip_like_unlike',
security: wpluip_ajax_obj.nonce,
post_id: postId,
action_type: 'like'
};
$.post(wpluip_ajax_obj.ajax_url, data, function(response) {
if (response.success) {
// Update displayed counts
container.find('.wpluip-like-count').text(response.data.like_count);
container.find('.wpluip-unlike-count').text(response.data.unlike_count);
alert(response.data.message);
} else {
alert(response.data.message);
}
});
});
// Handle Unlike Button Click
$(document).on('click', '.wpluip-unlike-button', function() {
const container = $(this).closest('.wpluip-vote-container');
const postId = container.data('post-id');
// Prepare AJAX data
const data = {
action: 'wpluip_like_unlike',
security: wpluip_ajax_obj.nonce,
post_id: postId,
action_type: 'unlike'
};
$.post(wpluip_ajax_obj.ajax_url, data, function(response) {
if (response.success) {
// Update displayed counts
container.find('.wpluip-like-count').text(response.data.like_count);
container.find('.wpluip-unlike-count').text(response.data.unlike_count);
alert(response.data.message);
} else {
alert(response.data.message);
}
});
});
});
3. Create the Css File
Customizing Additional CSS:
/* Adding like button functionality to WordPress posts */
.wpluip-like-button {
margin-top:15px;
max-width:150px;
background-color:#eee;
border-color:#888888;
color:#333;
display:inline-block;
vertical-align:middle;
text-align:center;
text-decoration:none;
align-items:flex-start;
cursor:default;
-webkit-appearence: push-button;
border-style: solid;
border-width: 1px;
border-radius: 5px;
font-size: 0.8rem;
font-family: inherit;
border-color: #000;
padding-left: 5px;
padding-right: 5px;
width: 100%;
min-height: 30px;
}
.wpluip-like-button a {
margin-top:4px;
display:inline-block;
text-decoration:none;
color:#333;
}
.wpluip-like-button:hover {
background-color:#888;
}
.wpluip-like-button:active {
background-color:#333;
}
.wpluip-like-button:hover a, .link-button:active a {
color:#fff;
}
/* Adding unlike functionality to WordPress posts */
.wpluip-unlike-button {
margin-top:15px;
max-width:150px;
background-color:#eee;
border-color:#888888;
color:#333;
display:inline-block;
vertical-align:middle;
text-align:center;
text-decoration:none;
align-items:flex-start;
cursor:default;
-webkit-appearence: push-button;
border-style: solid;
border-width: 1px;
border-radius: 5px;
font-size: 0.8rem;
font-family: inherit;
border-color: #000;
padding-left: 5px;
padding-right: 5px;
width: 100%;
min-height: 30px;
}
.wpluip-unlike-button a {
margin-top:4px;
display:inline-block;
text-decoration:none;
color:#333;
}
.wpluip-unlike-button:hover {
background-color:#888;
}
.wpluip-unlike-button:active {
background-color:#333;
}
.wpluip-unlike-button:hover a, .link-button:active a {
color:#fff;
}
4 Usage
- Activate the Plugin: Go to Plugins > Installed Plugins and activate “WP Like/Unlike by IP.”
- Insert the Shortcode:
- In the post content (e.g., the end of a blog post), add:
if you want to display buttons for a specific post outside of that post’s own page.
5. Test It:
- Visit the post on the front end.
- Click Like or Unlike and watch the counts update. If you try clicking again, you should get an error message saying you already voted from this IP.
6. Potential Enhancements
- User Accounts: Instead of IP-based tracking, you might require users to be logged in and store votes by user ID.
- Cookie Fallback: If you worry about dynamic IP addresses or proxies, you might combine IP checks with a browser cookie.
- Styling: Add custom CSS to style the buttons or place them in a more visually appealing layout.
- Security: For large sites, consider rate-limiting or more advanced checks to prevent malicious scripts from spamming votes.
- Display Past Votes: If you want to show whether the user already liked/unliked, you can check the IP in the page output and highlight the corresponding button.
Final Thoughts
This example provides a basic IP-based like/unlike system. Use it as a starting point and adapt it to your site’s requirements. If you later decide to expand functionality—like blocking repeat votes more robustly or switching from IP-based to user-based authentication—the structure above still applies.