You want to redirect a WordPress post to an external URL. Maybe it's an affiliate link, a resource that moved, or a curated link collection.
The naive approach using wp_redirect() with any URL opens security holes. Here's how to do it safely.
The Problem with wp_redirect()
// DON'T DO THIS
wp_redirect( $_GET['url'] );
exit;
This is an open redirect vulnerability. Attackers can craft URLs like:
yoursite.com/post?url=https://malicious-site.com
And use your domain's reputation to phish users.
Safe External Redirects
WordPress provides wp_safe_redirect(), but it only allows redirects to whitelisted hosts. For external URLs, we need validation:
/**
* Safely redirect to an external URL
*
* @param string $url Destination URL
* @param int $status_code HTTP status code (301, 302, 307)
*/
function safe_external_redirect( $url, $status_code = 301 ) {
// Validate URL format
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
wp_die( 'Invalid redirect URL' );
}
// Only allow http/https protocols
$allowed_protocols = array( 'http', 'https' );
$parsed = wp_parse_url( $url );
if ( ! isset( $parsed['scheme'] ) ||
! in_array( strtolower( $parsed['scheme'] ), $allowed_protocols, true ) ) {
wp_die( 'Invalid URL protocol' );
}
// Validate status code
$valid_codes = array( 301, 302, 307 );
if ( ! in_array( $status_code, $valid_codes, true ) ) {
$status_code = 301;
}
// Perform redirect
wp_redirect( esc_url_raw( $url ), $status_code );
exit;
}
Building a Post-Based Redirect System
Let's create a system where editors can set an external redirect URL per post.
Step 1: Add Meta Box
/**
* Add redirect meta box
*/
function add_redirect_meta_box() {
add_meta_box(
'external_redirect_box',
'External Redirect',
'render_redirect_meta_box',
'post',
'side',
'high'
);
}
add_action( 'add_meta_boxes', 'add_redirect_meta_box' );
/**
* Render meta box
*/
function render_redirect_meta_box( $post ) {
$enabled = get_post_meta( $post->ID, '_redirect_enabled', true );
$url = get_post_meta( $post->ID, '_redirect_url', true );
wp_nonce_field( 'redirect_meta_nonce', 'redirect_nonce' );
?>
<p>
<label>
<input type="checkbox" name="redirect_enabled" value="1"
<?php checked( $enabled, '1' ); ?>>
Enable external redirect
</label>
</p>
<p>
<label for="redirect_url">Destination URL:</label>
<input type="url" name="redirect_url" id="redirect_url"
class="widefat" placeholder="https://example.com"
value="<?php echo esc_attr( $url ); ?>">
</p>
<p class="description">
Visitors will be redirected to this URL when viewing this post.
</p>
<?php
}
Step 2: Save with Validation
/**
* Save redirect meta
*/
function save_redirect_meta( $post_id ) {
// Verify nonce
if ( ! isset( $_POST['redirect_nonce'] ) ||
! wp_verify_nonce( $_POST['redirect_nonce'], 'redirect_meta_nonce' ) ) {
return;
}
// Check permissions
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Skip autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Save enabled state
$enabled = isset( $_POST['redirect_enabled'] ) ? '1' : '0';
update_post_meta( $post_id, '_redirect_enabled', $enabled );
// Validate and save URL
$url = '';
if ( isset( $_POST['redirect_url'] ) ) {
$url = esc_url_raw( wp_unslash( $_POST['redirect_url'] ) );
// Double-check it's a valid URL
if ( ! empty( $url ) && ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
// Store error for admin notice
set_transient( 'redirect_error_' . $post_id, 'invalid_url', 30 );
return;
}
}
update_post_meta( $post_id, '_redirect_url', $url );
}
add_action( 'save_post', 'save_redirect_meta' );
Step 3: Handle Frontend Redirect
/**
* Handle redirect on frontend
*/
function handle_external_redirect() {
// Only on single posts
if ( ! is_singular( 'post' ) || ! is_main_query() ) {
return;
}
$post_id = get_the_ID();
$enabled = get_post_meta( $post_id, '_redirect_enabled', true );
$url = get_post_meta( $post_id, '_redirect_url', true );
if ( '1' !== $enabled || empty( $url ) ) {
return;
}
// Allow admins to bypass for testing
if ( current_user_can( 'edit_posts' ) && isset( $_GET['no_redirect'] ) ) {
return;
}
// Validate URL before redirecting
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
return;
}
// Redirect with 301 (permanent)
wp_redirect( esc_url_raw( $url ), 301 );
exit;
}
add_action( 'template_redirect', 'handle_external_redirect' );
Choosing the Right Status Code
| Code | Name | When to Use |
|---|---|---|
| 301 | Permanent | The old URL will never be used again. SEO value transfers. |
| 302 | Found | Temporary redirect. Original URL might come back. |
| 307 | Temporary | Like 302, but preserves HTTP method (POST stays POST). |
For most external redirects, 301 is correct. It tells search engines to update their index.
Modifying Permalinks in Listings
When a post has an external redirect, you might want links in menus and archives to point directly to the external URL:
/**
* Modify permalink for redirected posts
*/
function modify_redirected_permalink( $permalink, $post ) {
// Don't modify in admin
if ( is_admin() ) {
return $permalink;
}
$enabled = get_post_meta( $post->ID, '_redirect_enabled', true );
$url = get_post_meta( $post->ID, '_redirect_url', true );
if ( '1' === $enabled && ! empty( $url ) && filter_var( $url, FILTER_VALIDATE_URL ) ) {
return $url;
}
return $permalink;
}
add_filter( 'post_link', 'modify_redirected_permalink', 10, 2 );
Adding Admin Bar Link for Testing
Editors need to view the actual post (not get redirected) when testing:
/**
* Add "View Without Redirect" to admin bar
*/
function add_bypass_link( $wp_admin_bar ) {
if ( ! is_singular( 'post' ) || ! current_user_can( 'edit_posts' ) ) {
return;
}
$post_id = get_the_ID();
$enabled = get_post_meta( $post_id, '_redirect_enabled', true );
if ( '1' !== $enabled ) {
return;
}
$wp_admin_bar->add_node( array(
'id' => 'view-no-redirect',
'title' => 'View Without Redirect',
'href' => add_query_arg( 'no_redirect', '1', get_permalink() ),
'meta' => array( 'target' => '_blank' ),
) );
}
add_action( 'admin_bar_menu', 'add_bypass_link', 999 );
Security Checklist
-
Always validate URLs with
filter_var( $url, FILTER_VALIDATE_URL ) -
Sanitize on save with
esc_url_raw() -
Escape on output with
esc_url() - Check protocols only allow http/https
- Use nonces when saving meta
- Verify capabilities only editors should set redirects
- Never redirect to user input without validation
Common Issues
Redirect loops:
Make sure the redirect URL isn't the post's own URL. Add this check:
$current_url = get_permalink( $post_id );
if ( trailingslashit( $url ) === trailingslashit( $current_url ) ) {
return; // Don't redirect to self
}
Cached redirects:
If you change a redirect and it doesn't update, clear your site cache and browser cache. 301 redirects are aggressively cached.
SEO implications:
301 redirects pass ~90% of link equity. This is fine for legitimate use cases. But don't use redirects to manipulate search rankings that's against Google's guidelines.
If you want a complete solution with a settings page, redirect logging, and support for pages/CPTs, check out External Redirect Pro.
Resources:
Top comments (0)