Tracking post views helps you understand which content resonates with your audience. This data is valuable for content strategy, identifying popular topics, and displaying social proof. This solution uses WordPress post meta to store view counts efficiently without external services or plugins.

Why Track Post Views?

  • Content Strategy: Identify popular topics to create more relevant content
  • Social Proof: Display view counts to encourage engagement
  • Popular Posts Widget: Show most-viewed content automatically
  • Analytics: Track trends without relying on Google Analytics
  • Performance Insights: Compare post performance over time
  • Lightweight: No external API calls or plugin overhead

Basic View Counter

Track views and store count in post meta:

php
/**
 * Track post views
 */
function track_post_views($post_id) {
    if (!is_single()) {
        return;
    }

    if (empty($post_id)) {
        global $post;
        $post_id = $post->ID;
    }

    // Get current view count
    $count = get_post_meta($post_id, 'post_views_count', true);

    if ($count == '') {
        $count = 0;
        delete_post_meta($post_id, 'post_views_count');
        add_post_meta($post_id, 'post_views_count', '0');
    } else {
        $count++;
        update_post_meta($post_id, 'post_views_count', $count);
    }
}
add_action('wp_head', 'track_post_views');

/**
 * Display post views
 */
function get_post_views($post_id = null) {
    if (!$post_id) {
        $post_id = get_the_ID();
    }

    $count = get_post_meta($post_id, 'post_views_count', true);

    if ($count == '') {
        return '0 views';
    }

    return number_format($count) . ' views';
}

Advanced: Exclude Logged-in Users & Bots

Track only real visitor views, excluding admins and bots:

php
/**
 * Advanced post view tracking
 */
function advanced_track_post_views($post_id) {
    if (!is_single()) {
        return;
    }

    if (empty($post_id)) {
        global $post;
        $post_id = $post->ID;
    }

    // Exclude logged-in users
    if (is_user_logged_in()) {
        return;
    }

    // Exclude bots
    $user_agent = $_SERVER['HTTP_USER_AGENT'];
    $bot_patterns = array('bot', 'crawl', 'slurp', 'spider', 'mediapartners');

    foreach ($bot_patterns as $pattern) {
        if (stripos($user_agent, $pattern) !== false) {
            return;
        }
    }

    // Track unique views with cookie
    $cookie_name = 'post_view_' . $post_id;

    if (!isset($_COOKIE[$cookie_name])) {
        // Set cookie for 24 hours
        setcookie($cookie_name, '1', time() + 86400, COOKIEPATH, COOKIE_DOMAIN);

        // Increment view count
        $count = (int)get_post_meta($post_id, 'post_views_count', true);
        $count++;
        update_post_meta($post_id, 'post_views_count', $count);
    }
}
add_action('wp_head', 'advanced_track_post_views');

Display Views with Icon

Show formatted view count with visual icon:

php
/**
 * Display views with icon
 */
function display_post_views_with_icon($post_id = null) {
    if (!$post_id) {
        $post_id = get_the_ID();
    }

    $count = (int)get_post_meta($post_id, 'post_views_count', true);

    $output = '';
    $output .= '👁 ';

    if ($count >= 1000000) {
        $output .= number_format($count / 1000000, 1) . 'M';
    } elseif ($count >= 1000) {
        $output .= number_format($count / 1000, 1) . 'K';
    } else {
        $output .= $count;
    }

    $output .= ' views';

    return $output;
}

// Usage:
// echo display_post_views_with_icon();

Most Viewed Posts Query

Retrieve and display most popular posts:

php
/**
 * Get most viewed posts
 */
function get_most_viewed_posts($count = 5) {
    $args = array(
        'post_type'      => 'post',
        'posts_per_page' => $count,
        'meta_key'       => 'post_views_count',
        'orderby'        => 'meta_value_num',
        'order'          => 'DESC'
    );

    return new WP_Query($args);
}

/**
 * Display most viewed posts widget
 */
function display_popular_posts() {
    $popular_posts = get_most_viewed_posts(5);

    if ($popular_posts->have_posts()) {
        echo '';
        echo 'Most Popular';
        echo '';

        while ($popular_posts->have_posts()) {
            $popular_posts->the_post();
            $views = get_post_meta(get_the_ID(), 'post_views_count', true);

            echo '';
            echo '' . get_the_title() . '';
            echo '' . number_format($views) . ' views';
            echo '';
        }

        echo '';
        echo '';

        wp_reset_postdata();
    }
}

REST API Integration

Add view count to REST API responses:

php
/**
 * Add view count to REST API
 */
function add_views_to_rest_api() {
    register_rest_field('post', 'views', array(
        'get_callback' => function($post) {
            $views = get_post_meta($post['id'], 'post_views_count', true);
            return $views ? (int)$views : 0;
        },
        'schema' => array(
            'description' => 'Post view count',
            'type'        => 'integer'
        )
    ));
}
add_action('rest_api_init', 'add_views_to_rest_api');

Admin Column for Views

Show view counts in WordPress admin post list:

php
/**
 * Add views column to admin
 */
function add_views_column($columns) {
    $columns['post_views'] = 'Views';
    return $columns;
}
add_filter('manage_posts_columns', 'add_views_column');

/**
 * Display view count in admin column
 */
function display_views_column($column, $post_id) {
    if ($column === 'post_views') {
        $views = get_post_meta($post_id, 'post_views_count', true);
        echo $views ? number_format($views) : '0';
    }
}
add_action('manage_posts_custom_column', 'display_views_column', 10, 2);

/**
 * Make views column sortable
 */
function make_views_column_sortable($columns) {
    $columns['post_views'] = 'post_views';
    return $columns;
}
add_filter('manage_edit-post_sortable_columns', 'make_views_column_sortable');

/**
 * Sort by views
 */
function sort_by_views($query) {
    if (!is_admin()) {
        return;
    }

    $orderby = $query->get('orderby');

    if ('post_views' === $orderby) {
        $query->set('meta_key', 'post_views_count');
        $query->set('orderby', 'meta_value_num');
    }
}
add_action('pre_get_posts', 'sort_by_views');

Reset View Counts

Admin function to reset all view counts:

php
/**
 * Reset all post view counts
 */
function reset_all_post_views() {
    global $wpdb;

    // Delete all view count meta
    $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'post_views_count'");

    return true;
}

/**
 * Reset views for specific post
 */
function reset_post_views($post_id) {
    delete_post_meta($post_id, 'post_views_count');
    add_post_meta($post_id, 'post_views_count', '0');
}

Best Practices

Practice Why It Matters
Use Cookies for Unique Views Prevents multiple counts from same visitor refreshing page
Exclude Logged-in Users Authors checking their posts shouldn't inflate view counts
Filter Bot Traffic Search engine crawlers and bots skew analytics
Use Efficient Queries Index meta_key for fast sorting by view count
Consider Caching High-traffic sites should cache popular posts queries
Track in wp_head Ensures views are counted before any caching
Validate Post ID Always check post exists before updating meta
Use Transients Cache expensive "most viewed" queries for performance

Performance Impact

Performance: Low impact (0.002-0.005s per page view). Single post meta update per view is lightweight. For high-traffic sites (10k+ daily views), consider: 1) Batch updates using cron, 2) Store counts in separate table, 3) Use Redis/Memcached for counting, 4) Implement AJAX-based counting to avoid blocking page load. Most sites under 100k monthly views won't notice any performance impact.

Styling View Counts

css
/* View count styling */
.post-views {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 14px;
    color: #666;
}

.views-icon {
    font-size: 16px;
    opacity: 0.7;
}

/* Popular posts widget */
.popular-posts-widget {
    background: #f9f9f9;
    padding: 20px;
    border-radius: 8px;
}

.popular-posts-widget li {
    display: flex;
    justify-content: space-between;
    padding: 10px 0;
    border-bottom: 1px solid #eee;
}

.view-count {
    color: #999;
    font-size: 12px;
}