WordPress Attachment Page URL Rewrite!

There are three issues in your code:

  1. In your custom wp_attachment_link() function, you should use the post slug ($attachment->post_name) instead of simply replacing the (whitespaces) with - (dash) in the post title — and note that the resulting slug could be different than the actual slug, e.g. the title could be My Image with the actual slug being my-image-2, and yet your str_replace()-ing would result in my-image.

  2. In your rewrite rule, there’s no $matches[2], only $matches[1] which matches the <slug> as in example.com/photos/<slug>.

  3. Also in that rule, the query is not valid: You should use the attachment parameter and not attachment_id, so the correct one is attachment=$matches[1].

So that should help you fix your own code, or you can try my code:

add_filter( 'attachment_link', 'wp_attachment_link', 20, 2 );
function wp_attachment_link( $link, $attachment_id ) {
    if ( ! $slug = get_post_field( 'post_name', $attachment_id ) ) {
        return $link; // you should just do this if the slug is empty..
    }

    return home_url( user_trailingslashit( "/photos/$slug", 'single' ) );
}

// Rewrite attachment page link.
add_action( 'init', function() {
    add_rewrite_rule( // wrapped for brevity
        '^photos/([\w\-]+)/?$',
        'index.php?attachment=$matches[1]',
        'top'
    );
} );

And don’t forget to flush the rewrite rules; just visit the permalink settings admin page (wp-admin → Settings → Permalinks). Also, you should use a unique function name and not wp_attachment_link, e.g. use my_prefix_attachment_link. 🙂