Automatically Write Post Summaries in WordPress with ChatGPT

James Parsons by James Parsons Updated Mar 28th, 2024 6 Comments

While I'm not a fan of using AI or ChatGPT for writing entire blog posts, it can be useful for some smaller tasks.

In this case, I created a plugin that automatically summarizes the most important points in each of my blog posts and adds that quick 30 second summary before the post gets started.

Example

This plugin takes your entire blog post, sends it to ChatGPT, and then asks it to summarize your most important points and facts in a quick highlights section. Then, it adds that summary to your post for you, automatically. It's also designed to automatically drip-feed these out to your existing blog posts over time. You can see the above example on one of my posts, here.

Cool, huh?

Why? Well, there's some evidence that this is good for user experience. Many blog posts are long-winded ways of trying to explain a fairly simple answer, and this gives the user the short answer that they need right away. Then they can continue reading if they find it interesting. It gets people information quicker, and that's the essence of the latest Helpful Content Updates; this makes your content more helpful.

Features

Here's what this plugin does:

  • Fully automated. You add it to your functions.php, add your ChatGPT API key, and it handles the rest. I added detailed instructions below, which we'll get to in a second.
  • Drip-fed. There is a cron schedule in this code that runs twice per day to slowly drip-feed these into your existing blog posts. The process can also be manually triggered on any post by adding ?generate-summary to the end of your blog post URLs.
  • Automatic inserting. This is inserted automatically into your blog posts before the first <h2> heading in your article, but only if ?generate-summary is ran, either manually or from the twice-daily cron job. If you have custom post types or want it to only show up on a certain page type, you can modify is_single() to be something else (for example, if you only want it to show up in the "blog" parent, you could use: strpos($_SERVER['REQUEST_URI'], 'blog') !== false).
  • Post updating. When a summary is added to the post, it also automatically updates the post "Updated" or "Modified" date. Since your content changed, it's important to update the date that your post was last modified. If you show this on your blog posts (instead of the publish date), this will show up in search results, too.
  • Redo link. There's a link added on the front-end that you can click and it will automatically re-generate the description if you don't like the first one. This link is only visible to admins that are logged in. The link is titled: "Re-Generate ChatGPT Summary".
  • Formatting. This will do some basic formatting of ChatGPT's replies. ChatGPT loves to use 'single quotes' instead of "double quotes", and it also over-uses mdashes (—). These are both easily trackable ChatGPT patterns, so we swap these out.
  • Prompt engineering. I included a default prompt to summarize the post in a simple and to-the-point way without using all of the unnatural AI words that it likes to use (delve, opt, enhancing, offering, significant, encompassing, etc). This prompt limits the summary to around 75 words too; you want this to be a quick summary, and the less runway that AI has, the better the quality will be. Plus, you want the large majority of your content to stay human written.
  • Caching. These answers are fetched once and then stored in your database. If a summary has already been generated, running ?generate-summary again won't generate a new summary. You'll have to manually visit the post and click the the "Re-Generate ChatGPT Summary" link if you're not happy with the response or if you want something new.
  • Easily removable. The content is stored in your database sepreately from your blog posts, so removing this code from your functions.php will also remove all of these 30 second summaries from all of your posts. This makes it very easy to remove in-case you decide you don't want this later.

Other Notes

  • These summaries are stored in your WordPress database under the table post_meta with the key "chatgpt_summary". These are easily to find and bulk edit or delete in PHPMyAdmin or your database management software.
  • You can delete the cron at the bottom if you don't want it to run automatically twice a day. Just delete everything after: // Cron to run this twice daily, slowly drip feed to old posts. Or, you can modify this to increase the speed by creating a custom cron that runs more frequently.
  • You may want to add a hook in here to automatically generate one each time you publish a new post. For now, this has to be done manually (or you have to wait for the cron to get around to it sometime in the future).

Steps to Install

  • Sign up for ChatGPT Plus. Then, head over to your API Keys section to create a new "secret key". This is how your plugin will authenticate with ChatGPT.
  • Copy your secret key and add it to the code below under the $apiKey section. It should look like this when you're done: $apiKey = 'sk-123456789123456789123456789'; // Add ChatGPT key here
  • Optional: Modify the front-end styling and language. You can add CSS, change the title, add images, or otherwise pretty this up any way that you'd like. You can always do this later.
  • Copy this code and add it to the bottom of your functions.php file. This is located at: /wp-content/themes/your-theme-name/functions.php . I recommend that you use FTP and a text/code editor for this purpose, as editing theme files within WordPress can temporarily break your site if you add a typo into the code, and it's easy to fix these issues with FTP.
  • Test to confirm it works. You can visit a blog post, and then manually add ?generate-summary to the end of the URL. It should take some time to think, and then the page will refresh and you'll see the new summary added above your first <h2> heading in your blog post. These will automatically be slowly added to each of your posts, twice per day, without having to manually generate them. But, you're welcome to manually generate as many as you'd like on your own.
  • All done!

Here is the code:

// Generate ChatGPT summary before posts

function requestChatGPTSummary($postContent) {
  $apiKey = 'sk-your_key_here'; // Add ChatGPT key here 
  $apiUrl = 'https://api.openai.com/v1/chat/completions'; 
  // Leave prompt as-is unless you are skilled with prompt engineering
  $firstQuery = 'Summarize the most important points in my blog post in under 75 words. Here are my strict guidelines: 
  - This is my article, so NEVER refer to it in the third person as "This guide", "This article", and so on; dont draw attention to the article or the author itself.  State the most important facts, options, answers, and information directly as if its general knowledge, rather than something specifically found within this piece of writing.
  - Simplify all vocabulary without oversimplifying the meaning of the content. Choose the most common, direct word that accurately conveys your meaning. Ask yourself, "Could I explain this concept to someone with a basic understanding of my industry?" If the answer is no, search for a simpler alternative. Avoid overly formal, stilted phrasing in favor of natural language patterns that feel more conversational, without skewing towards unprofessional or slang. For example, "aiming" should be "wanting", "opt" should be "choose", "For instance" should be "For example". Avoid these less common words and choose ones that are more conversational.
  - Avoid ALL words that end in the letters "ing" (also known as gerunds). Do NOT use any of these. Scan your response before sending it to me to make sure there are no words ending in "ing" anywhere in the response.
  Please respond only with the summary and no other acknowledgements or confirmations. Your reply should be less than 75 words, so focus on only the most important facts:
  -- ';
  $postData = [
    'model' => 'gpt-4', // Change as needed, use gpt-4 for best results
    'messages' => [
          [
              'role' => 'system',
              'content' => 'You are a helpful assistant.'
          ],
          [
              'role' => 'user',
              'content' => $firstQuery . $postContent
          ]
      ],
    ];
  $ch = curl_init($apiUrl);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Content-Type: application/json',
      'Authorization: Bearer ' . $apiKey,
  ]);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
  $response = curl_exec($ch);
  curl_close($ch);
  $responseDecoded = json_decode($response, true);
  if (isset($responseDecoded['choices'][0]['message']['content'])) {
      return trim($responseDecoded['choices'][0]['message']['content']);
  } else {
      return 'Summary unavailable. Check response or configuration.';
  }
}

// Save and fetch summary from DB

function getPostSummary($postId) {
  return get_post_meta($postId, 'chatgpt_summary', true);
}
function savePostSummary($postId, $summary) {
  update_post_meta($postId, 'chatgpt_summary', $summary);
}

// Swap single quotes for double, ChatGPT likes to use 'quotes' instead of "quotes"

function swapOuterSingleQuotesForDouble($text) {
  $pattern = "/(?<=\s)'(.*?)'(?=\s|,|\.)/";
  $replacement = '"$1"';
  return preg_replace($pattern, $replacement, $text);
}
add_filter('the_content', 'custom_gpt_content_filter', 9999);
function custom_gpt_content_filter($content) {
  global $post;
  global $wpdb;
  $summary = getPostSummary($post->ID);
  // If GPT is in the query string, start the process
  if (is_single() && isset($_GET['generate-summary'])) {
    // If theres no summary yet, make one
    if (empty($summary)) {
        $summary = requestChatGPTSummary(wp_strip_all_tags($content));
        $summary = str_replace('—', '; ', $summary);
        $summary = swapOuterSingleQuotesForDouble($summary);
        savePostSummary($post->ID, $summary);
        update_post_modified_time($post->ID);
    }
  }
  
  // If a summary has already been generated in the past, lets show it
  
  if (is_single() && !empty($summary)) {
    $delete_link = '';
    if (is_user_logged_in() && current_user_can('activate_plugins')) {
      $delete_link = ' <a href="'. esc_url( get_the_permalink() ) . '?delete_gpt_post_meta='. esc_attr($post->ID) .'" 
        id="delete-gpt-post-meta" 
        onclick="return confirm(\'Are you sure you want to regenerate the GPT post meta?\')">Re-Generate ChatGPT Summary</a>';
    }
    $toc_end_position = strpos($content, '</div>', strpos($content, 'class="lwptoc"')); // This ensures it gets added after your Table of Contents. I use Lucky WP Table of Contents, which has the class "lwptoc". If you use a different one, add the class of its container here. If you don't use a Table of Contents plugin, you can ignore this.
    $summaryWithHeading = "<div class='summary'><h2>Post Summary</h2><p>$summary</p>{$delete_link}</div>";
    $content = substr_replace($content, $summaryWithHeading, strpos($content, '<h2>'), 0);
  }
  return $content;
}

// Show a "regenerate summary" link for admins only, this lets you create a new one if you don't like the one it came up with

add_action('template_redirect', 'delete_gpt_post_meta');
function delete_gpt_post_meta() {
  if (is_user_logged_in() && current_user_can('activate_plugins') && isset($_GET['delete_gpt_post_meta'])) {
    global $wpdb;
    $post_id = intval($_GET['delete_gpt_post_meta']);
    $meta_key_like = "%" . $wpdb->esc_like('gpt') . "%";
    $sql = $wpdb->prepare(
      "DELETE FROM {$wpdb->prefix}postmeta WHERE post_id = %d AND meta_key LIKE %s",
      $post_id,
      $meta_key_like
    );
    $deleted_rows = $wpdb->query($sql);
    error_log("Deleted rows count: " . $deleted_rows); 
    $current_url = remove_query_arg('delete_gpt_post_meta');
    $redirect_url = add_query_arg( 'gpt', '', $current_url );
    wp_safe_redirect($redirect_url);
    exit();
  }
}

// Update the post "modified" date after adding this since the content was updated

function update_post_modified_time($post_id){
  global $wpdb;
  $current_time = current_time('mysql');
  $current_gmt_time = current_time('mysql', 1);
  $wpdb->query(
      $wpdb->prepare(
          "UPDATE $wpdb->posts SET post_modified = %s, post_modified_gmt = %s WHERE ID = %d", 
          $current_time, 
          $current_gmt_time, 
          $post_id
      )
  );
  clean_post_cache($post_id);
}

// Cron to run this twice daily, slowly drip feed to old posts

add_action('wp', 'register_gpt_summary_preloader_event');
function register_gpt_summary_preloader_event() {
    if (!wp_next_scheduled('gpt_summary_preloader_event')) {
        wp_schedule_event(time(), 'twicedaily', 'gpt_summary_preloader_event');
    }
}
add_action('gpt_summary_preloader_event', 'execute_gpt_summary_preloader');
function execute_gpt_summary_preloader() {
    include_once(ABSPATH . 'wp-admin/includes/post.php');
    $args = array('post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1);
    $posts = get_posts($args);
    if (!empty($posts)) {
        $random_post = $posts[array_rand($posts)];
        $post_url = get_permalink($random_post->ID);
        $url_with_gpt = add_query_arg('generate-summary', '', $post_url);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url_with_gpt);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, 'SitePreloader'); // If you use Cloudflare, create a new rule that bypasses the cache for this user agent
        $response = curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
    }
}

?>

What do you think of this plugin? Have you found a way to improve it? Any questions for me? Please leave me a comment below!

Related Code Snippets

Written by James Parsons

James Parsons is the founder and CEO of Content Powered, a premier content marketing agency that leverages nearly two decades of his experience in content marketing to drive business growth. Renowned for founding and scaling multi-million dollar eCommerce businesses through strategic content marketing, James has become a trusted voice in the industry, sharing his insights in Search Engine Watch, Search Engine Journal, Forbes, Entrepreneur, Inc, and other leading publications. His background encompasses key roles across various agencies, contributing to the content strategies of major brands like eBay and Expedia. James's expertise spans SEO, conversion rate optimization, and effective content strategies, making him a pivotal figure in the industry.