I'm trying to make a page where a user provides an ID of a attachment and the site will set the headers and deliver that file instead of a 404 page. so /download/1754 would lookup the attachment with that Id and output it for viewing/download depending on the file type.

I just can't think of a wordpress friendly way to do this from inside a plugin. I considered using add_rewrite_rule but couldn't see a good way around it, also considered checking $_SERVER['REQUEST_URI'] to deliver the content before any HTML but then I'd have to stop the page from rendering a 404 and I was not sure of a Wordpress friendly way to do this.

My current idea was to have my plugin make a download page, assign a template and have that template run the logic instead of my plugin class. while I have used this before for another situation it was very messy as I had to write a lot of code to force users to be unable to update or edit the file and recreate/delete it on activation.deactivation. so ideally I'd like to avoid that.

  • 2
    Have a look at the template_redirect action - codex.wordpress.org/Plugin_API/Action_Reference/…. This action will allow you to check whatever you want, such as $_SERVER['REQUEST_URI'], and if necessary redirect the user using wp_safe_redirect(). Have a bash, if you struggle update your questions with your results and we can help more :)
    – David Gard
    Commented Jan 26, 2015 at 15:39
  • @DavidGard There's wp_get_referer() for that.
    – kaiser
    Commented Jan 27, 2015 at 22:48

2 Answers 2


You're on the right path with a rewrite rule. If you're just delivering a file, you can hook an early action, check if a download was requested, then output the file and exit before WordPress ever gets to the stage of querying the database and deciding the request is a 404.

// add the download ID query var which we'll set in the rewrite rule
function wpd_query_var( $query_vars ){
    $query_vars[] = 'download_id';
    return $query_vars;
add_filter( 'query_vars', 'wpd_query_var' );

// add rewrite rule
function wpd_rewrite(){
add_action( 'init', 'wpd_rewrite' );

// check for download_id and output the file
function wpd_request( $wp ){
    if( array_key_exists( 'download_id', $wp->query_vars ) ) {
        // output your headers and file here
        // ID is in $wp->query_vars['download_id']
        // then exit to halt any further execution
add_action( 'parse_query', 'wpd_request' );
  • 1
    You have to flush rules after adding or changing them. normally you'd do that on plugin activation/deactivation, but you can also just visit the permalinks settings page.
    – Milo
    Commented Jan 26, 2015 at 17:05
  • I already have the code for auto flushing git on plugin activation and have tried it manually repeatedly. it's picking up a matched query_of 'attachment=3F8750942A' with the code being the test code I entered in the URL. Annoyingly this is probably the simplest use of rewrite rules I've done and obviously this is the one that's a pain. Commented Jan 26, 2015 at 17:17
  • oops, a couple of typos, sorry. forgot to return $query_vars, and the correct hook is parse_query.
    – Milo
    Commented Jan 26, 2015 at 17:38
  • I'd already fixed those previously. Seems once again it is an issue with wordpress, add_rewrite_rule not allowing an $ at the end where as add_rewrite_rules does allow it. Commented Jan 26, 2015 at 17:44
  • I've tested in twentyfourteen and it's working as expected.
    – Milo
    Commented Jan 26, 2015 at 18:05

You can try the following example, for image attachments:

add_action( 'init', function()
    add_rewrite_endpoint( 'download', EP_ROOT );
    add_action( 'template_redirect', function()
        if ( $pid = get_query_var( 'download' ) ) 
            $found = false;
            if( $file = wp_get_attachment_image_src( absint( $pid ), 'full' ) )
                $found = true;
                header( "Content-Disposition: attachment; filename=" . basename( $file[0] ) );
                readfile( $file[0] );
            if( ! $found )
                wp_die( __( 'Image file not found!' ) );
    } );   
} );

where you might have to adjust the headers to your needs.

Re-generate your rewrite rules and then you should be able to download the image attachments from:


There are much better scripts out there, compared to this simple demo, but it seems to work on my install. Check for example out this blog, it seems to have some interesting ideas on how to handle large files.

Hopefully you can modify it to your needs.

  • The link is great. I hope you move some of the suggestions from there to here as well. Useful info that should be doubled to not go away :)
    – kaiser
    Commented Jan 27, 2015 at 23:03
  • Yes there are plenty of useful information on that blog post (ps: this blog is unrelated to myself). I sometimes wish SE would allow attachments to answers or keep a service like the WayBackMachine ... hey let me check, ... found the archived blog post here ... just in case ;-)
    – birgire
    Commented Jan 28, 2015 at 10:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.