Usually when a published post expires and is switched to draft status, navigating to the old URL will return a 404 Not Found.
We’re going to detect this condition and redirect users to a specific URL for expired/draft posts instead. This won’t affect the regular 404 page, which will continue to function normally… only draft posts will redirect.
This is really useful if you’ve got content that expires, like events or educational courses.
Check conditions, then redirect 301
The logic for this mini-project breaks down like this:
- Hook WooCommerce early in the page-load sequence to detect a 404 being generated. We can’t hook the “init” action because the main query isn’t set up by that stage. But we can hook the “wp” action and access the global
$wp_query
object from there. - If the user is not logged-in and the current page is a 404, then…
- Try to get the slug of the current post. If this succeeds and the post has a status of “draft”, then…
- Redirect to an alternative URL
- else…
- Carry on as normal – WordPress will show the standard 404 page.
- Try to get the slug of the current post. If this succeeds and the post has a status of “draft”, then…
Create the PHP code
In your custom child theme, create a file called redirect-draft-posts.php and paste the following into it:
<?php /* * WP Tutorials :: Redirect Draft Posts (WPTRDP) * * https://wp-tutorials.tech/add-functionality/redirect-draft-posts-to-a-url-instead-of-404/ */ // Block direct access. defined('WPINC') || die(); // The URL to redirect // ** Change this to the URL you want to redirect to ** const WPTRDP_REDIRECT_DRAFTS_TO_URL = 'https://devx.headwall.tech/content-no-longer-available/'; /** * If someone lands on a URL that used to be a published post but is now a * draft, redirect to an alternative URL. */ function wptrdp_maybe_redirect_draft_404() { // Pick up the current global WP Query object. global $wp_query; if (is_user_logged_in()) { // If the user is logged in, don't redirect. } elseif (empty($wp_query)) { // No global query? Weird. } elseif (! $wp_query->is_404) { // We're not on a 404 page. } elseif (empty(($post_name = $wp_query->get('name')))) { // We don't have a post slug. Weird. } else { // OK. We're on a 404 post and we know the name. // Let's see if we can find a draft post with the same slug. $query = [ 'name' => $post_name, 'post_status' => 'draft', 'posts_per_page' => 1, ]; $posts = get_posts($query); if (is_array($posts) && count($posts) === 1) { // We've found an exact match for a Draft post - redirect now. wp_redirect(WPTRDP_REDIRECT_DRAFTS_TO_URL, 301); exit(); } } } add_action('wp', 'wptrdp_maybe_redirect_draft_404');
Edit your child theme’s functions.php file and add the following two lines into it:
// Headwall WP Tutorials : Redirect draft posts require_once dirname(__FILE__) . '/redirect-draft-posts.php';
Save everything and reload your site to make sure nothing is broken.
Create a page or a post on your site that you want to act as your draft-posts redirect page, grab its URL and set the WPTRDP_REDIRECT_DRAFTS_TO_URL
constant in “redirect-draft-posts.php” to your URL.
Try to view a draft post by its full URL on your site while you’re logged-in and you’ll see the post load normally.
Open a private browsing window and try the same URL – it should now redirect to your alternative URL.
As a final test, use curl from the command-line to verify that the draft post URL returns a 301 response code (redirect) instead of a 404 (not found) like this:
# Check the HTTP Response Headers for a single URL curl -I "https://devx.headwall.tech/demos/test-draft-post-do-not-publish/"
That’s all there is to it.
Test for various conditions… if those conditions are met, redirect to an alternative URL 😎 👍