Add Estimated Reading Time to WordPress Posts (No Plugin, Custom PHP Guide)

Learn how to add an estimated reading time to your WordPress posts using a simple PHP snippet. Our step-by-step, no-plugin guide helps boost user engagement and SEO.

Add Estimated Reading Time to WordPress Posts (No Plugin, Custom PHP Guide)

Have you ever landed on a blog post and seen a small "7 min read" indicator near the title? It's a small detail, but it's a powerful user experience feature. It helps set expectations and encourages visitors to commit to reading your content. While there are plenty of WordPress plugins that can add this feature, they often come with extra overhead, settings, and potential performance impacts.

As a developer, I often prefer a more lightweight, custom solution. I want full control over the logic and where the data is stored, without adding another plugin to my stack if I can avoid it.

Today, I'm going to share a simple yet powerful PHP snippet I created to automatically calculate the reading time for any post and save it to a custom field. I'll walk you through the code step-by-step, explain why each function is necessary, and then show you exactly how to implement it on your own WordPress site.

The Goal: A Lightweight, Automated Solution

My objective was simple:

  1. When a post is published or updated, automatically calculate the estimated reading time.
  2. Save this value (in minutes) to a custom field. For this guide, I'll use a field named read_time.
  3. Provide a settings page to retroactively update the reading time for all previously published posts.

A Quick Prerequisite: This code is designed to work with a custom field. My preferred tool for this is Advanced Custom Fields (ACF). Before you start, you'll need to have a custom field plugin installed and have created a Number field named read_time ⁣assigned to your posts.

The Code: A Complete Reading Time Calculator

Here is the full PHP snippet I created. I'll break it down function by function below.

<?php
/**
 * Calculate reading time from content.
 *
 * @param string $content Post content.
 * @return int Reading time in minutes.
 */
function calculate_reading_time( $content ) {
    $word_count = str_word_count( strip_tags( $content ) );
    // The average reading speed is around 200 words per minute.
    return ceil( $word_count / 200 );
}

/**
 * Update reading time for a post when it's saved.
 *
 * @param int $post_id Post ID.
 */
function update_post_reading_time( $post_id ) {
    // I don't want to run this on revisions or autosaves.
    if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
       return;
    }
    
    $content = get_post_field( 'post_content', $post_id );
    $reading_time = calculate_reading_time( $content );
    
    // This function is from Advanced Custom Fields (ACF).
    update_field( 'read_time', $reading_time, $post_id );
}

/**
 * Update reading time for all existing posts.
 */
function update_all_reading_times() {
    $posts = get_posts( array(
       'post_type' => 'any',
       'numberposts' => -1,
       'post_status' => 'publish'
    ) );
    
    foreach ( $posts as $post ) {
       update_post_reading_time( $post->ID );
    }
}

/**
 * Add the admin menu page under "Settings".
 */
function reading_time_admin_menu() {
    add_options_page(
       'Reading Time Settings', // Page Title
       'Reading Time',          // Menu Title
       'manage_options',        // Capability required
       'reading-time',          // Menu Slug
       'reading_time_admin_page' // Function to display the page
    );
}

/**
 * Render the admin page for bulk reading time updates.
 */
function reading_time_admin_page() {
    // Check if the form was submitted and verify the nonce for security.
    if ( isset( $_POST['update_all'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'update_reading_times' ) ) {
       update_all_reading_times();
       echo '<div class="notice notice-success"><p>Reading times updated for all posts!</p></div>';
    }
    ?>
    <div class="wrap">
       <h1>Reading Time Settings</h1>
       <form method="post">
          <?php wp_nonce_field( 'update_reading_times' ); ?>
          <p>Click the button below to calculate and update the reading time for all existing published posts. This may take a moment on sites with many posts.</p>
          <input type="submit" name="update_all" class="button-primary" value="Update All Reading Times">
       </form>
    </div>
    <?php
}

// Hook to update reading time automatically when a post is saved.
add_action( 'save_post', 'update_post_reading_time' );

// Hook to add the admin menu page.
add_action( 'admin_menu', 'reading_time_admin_menu' );

Deconstructing the Code: How It Works

Let's walk through each piece of the snippet.

So, what does this code do?

  • It creates our function, get_the_reading_time.
  • I've set a default reading speed of 200 words per minute (WPM), which is a pretty standard average.
  • It cleans up the post content, removing HTML so we only count the actual words.
  • It does a little math: divides the word count by the WPM to get the time.
  • It formats the result into the "X min read" text we want to see.

calculate_reading_time( $content )

This is the core logic. It takes the post's content as input and does two things:

  1. strip_tags( $content ): It removes all HTML tags from the content to ensure I am only counting the actual words, not code.
  2. str_word_count(...): This PHP function counts the words in the resulting plain text.
  3. ceil( $word_count / 200 ): The final step is to divide the word count by 200 (the average reading speed of an adult) and use ceil() to round the result up to the nearest whole number. This gives me the reading time in minutes.

update_post_reading_time( $post_id )

This function is the automatic trigger. It's hooked into save_post, so it runs every time a post is saved.

  • It first checks if the save event is for a revision or an autosave. If it is, I return early to avoid running the code unnecessarily.
  • It then gets the post's content, calls my calculate_reading_time() function to get the value, and finally uses ACF's update_field() function to save the integer to the read_time custom field for that specific post.

update_all_reading_times()

This function is for handling all the content I've already published. It uses get_posts() to fetch an array of all published posts (of any post type). Then, it loops through each one and calls update_post_reading_time(), effectively updating the read_time field for my entire back catalog.

reading_time_admin_menu() and reading_time_admin_page()

These two functions work together to create the settings page.

  • reading_time_admin_menu() uses WordPress's add_options_page() to add a new sub-menu item called "Reading Time" under the main "Settings" menu in the admin dashboard.
  • reading_time_admin_page() contains the HTML for that page. It's a simple page with a title, a short explanation, and a button. When the button is clicked, it submits a form. The function checks if the form was submitted, verifies the _wpnonce for security (to prevent cross-site request forgery), and if everything checks out, it calls my update_all_reading_times() function and displays a success message.

How to Add This to Your WordPress Site

Now for the fun part. Here's how to get this up and running.

Step 1: Create the Custom Field

Make sure you have a plugin like Advanced Custom Fields installed. Create a new Number field with the name read_time. Assign it to appear on the post types you want to have a reading time (e.g., Posts).

Step 2: Add the PHP Code

You have two main options for adding this code:

  1. (Recommended) A Custom Plugin: This is the best practice. You can create a simple plugin file, paste the code in, and activate it. This way, your functionality isn't tied to your theme.
  2. (Easy) Your Theme's functions.php file: For a quick and easy implementation, you can paste this entire code snippet at the end of the functions.php file in your active theme (preferably a child theme, so your changes aren't lost on a theme update).

Step 3: Update Your Existing Posts

Once the code is added, navigate to Settings > Reading Time in your WordPress admin dashboard. You will see the simple page I created. It's a basic page with only one action (for now).

Click the "Update All Reading Times" button. This will loop through all your old posts and calculate the reading time for each one.

Step 4: Display the Reading Time on Your Site

The final step is to show this value to your visitors. You'll need to edit your theme's template files. This is typically single.php for single posts, or a template part that handles post meta.

Find the spot where you want to display the reading time (e.g., near the author name or date) and add the following PHP snippet:

<?php
$read_time = get_field('read_time');
if ($read_time) {
    echo '<span>' . $read_time . ' min read</span>';
}
?>

This code gets the value from the read_time field and, if it exists, prints it out wrapped in a <span> tag.

Conclusion

And that's it! With one block of code and a few simple steps, you've added a valuable, lightweight feature to your WordPress site. You have full control over the logic, no extra plugin weighing you down, and a seamless, automated workflow for all future posts. It's a perfect example of how a little custom code can provide a clean and efficient solution.

Great! Next, complete checkout for full access to Piotr Krzyzek.
Welcome back! You've successfully signed in.
You've successfully subscribed to Piotr Krzyzek.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info has been updated.
Your billing was not updated.