Trailing slashes on urls don’t confuse browsers. But Google Search Console considers a url with a trailing slash as different from the same url without the trailing slash. Therefore, all links to your site (including internal links) should have the trailing slash or not according to your setting in Settings>Permalinks. This post will show you how to clean up an existing site by adding a trailing slash to urls in your content.
First you should check your Permalinks setting to see your site’s default behavior. Check for the trailing slash (or not) in the Custom structure field.
Has trailing slash
Trailing slash not required (code not needed or modify to remove the slash rather than add)
The following code is designed to add a trailing slash to your internal links in the post content. Any links to your home page (site_url) are ignored.
STEPS:
- Add the code to your custom plugin or functions.php file.
- Backup your posts table. Don’t forget this step! All changes are permanent.
- Enable WP_DEBUG and WP_DEBUG_LOG and make certain the site is logging errors. This is optional but the debug.log is the only output of results. Feel free to change error_log to var_dump if you know the difference and have a preference. I would not recommend for large sites.
- Add ?slash=yoursitenamestring to your home url. Where yoursitenamestring is a string you expect to be in the internal links you wish to fix. Leave out the protocol (http:// or https://). And unless you specifically want to specify the domain suffix (.com, .net, etc) leave it off too. So for the site https://anvilzephyr.com, I would only use slash=anvilzephyr
- Visit the new url to run the function
- Check the debug.log for results. Posts containing no links are skipped.
if (isset($_GET['slash']) && !is_null($_GET['slash'])) globalcom_add_ending_slash (); function globalcom_add_ending_slash(){ $string = sanitize_text_field($_GET['slash']); $home = site_url(); global $wpdb; $sql = "SELECT ID, post_content from $wpdb->posts where post_status IN ('publish', 'draft', 'pending') and post_type !='attachment'"; $results = $wpdb->get_results($sql); error_log(count($results).' to process'); // Find links foreach ($results as $post){ $post_id = $post->ID; error_log('Processing '.$post_id); $updated = 0; $content = $post->post_content; $parts = explode('href=',$content); if (count($parts)==1) continue; // eliminate chaff $meh = array_shift($parts); foreach ($parts as $piece){ if (!strpos($piece,'>')) { error_log('Weird - '.$piece); continue; } //determine quote type $single = stripos($piece, "'"); $double = stripos($piece, '"'); $quote = $single < $double? "'": '"'; // trim the first quote $trimmed_piece = ltrim($piece, $quote); // get the href $bits = explode($quote, $trimmed_piece, 2); // cleanup href $link = trim(filter_var($bits[0], FILTER_SANITIZE_URL)); //var_dump($link); // Leave links to home alone if ($link == $home){ error_log('Ignored - '.$link); continue; } // check for external link if (stripos( $link, $string) === false){ error_log('External - '.$link); continue; } if (substr($link, -1) == '/'){ error_log('OK '.$link); continue; } $new_link = $link.'/'; $new_content = str_replace($link,$new_link,$content); if (wp_update_post( ['ID' => $post_id, 'post_content' => $new_content] ) ){ error_log('Updated - '.$link.' to '.$new_link); } } } }
Example of result:
https://anvilzephyr.com/support
becomes
https://anvilzephyr.com/support/
This code doesn’t alter the protocol as that is fairly simple to do with a REPLACE clause in the database table. Or use a plugin like Go Live Update Urls.