For me this is quite hard to do, but I guess that this could be easily done by an expert. :)

Let's suppose I have a post, which is a custom post type resources, with the following data:

  • Post Slug: beach
  • Custom Fields:
    • dl1file: bikini
    • dl1link: assets/file1.zip
    • dl1count: 56
    • dl2file: beachball
    • dl2link: assets/file2.zip
    • dl2count: 31

The post above have more than one file to download, and all of them are stored in Custom Fields as dlNfile, where N are numbers (e.g.: dl1file). Also, each of these files have two more custom fields, the dlNlink (e.g.: dl1link) which will be used in the URL, and dlNcount (e.g.: dl1count) will store the download count number.

Now, with Rewrite API I will need to convert site.com/?resource=beach&file=bikini (or if it's easier, site.com/resources/beach/?file=bikini) into site.com/download/beach/bikini.

When one of the links is clicked, and since I only provided the resource, which is the post slug, and the value of one of the dlNfile, WordPress will now have to know which post to retrieve and identify the correct dlNfile, which in this case is dl1file. Then it will know that the real filename is stored in dl1link and then it proceeds to the download (site.com/assets/file1.zip) and increments dl1count by 1.

EDIT: I tried Jesper's tutorial before, but I don't want to create a new post just for store a download file information.

I also provided another URL idea, which doesn't have to store the URL parameter resource, which is the post slug.

  • I can see that you tried :), but this question is confusing. Can you rewrite this a little to make it less hard to follow?
    – s_ha_dum
    Commented Jan 20, 2013 at 0:00
  • @s_ha_dum I'm afraid I can't make this easier to explain, but I tried to improve the question a little bit. :) Commented Jan 20, 2013 at 0:58
  • what happens when someone visits one of these URLs? does the download start immediately or does the user see a page?
    – Milo
    Commented Jan 20, 2013 at 2:10
  • It's supposed to start immediately. And if possible, it keeps showing the parent page (site.com/resources/beach). Commented Jan 20, 2013 at 11:29
  • I edited the question. I provided another URL solution to be rewrited and also posted the tutorial I followed before and the reason why I don't want to follow it. Commented Jan 20, 2013 at 12:50

1 Answer 1


The first step in making this work is to register your custom query var for the file via the query_vars filter. I've named it wpa82328_file, but you can change this to something more meaningful to you. file is a bit generic though, so you'll want to prefix it with something guaranteed to be unique:

function wpa82328_query_vars( $query_vars ){
    $query_vars[] = 'wpa82328_file';
    return $query_vars;
add_filter( 'query_vars', 'wpa82328_query_vars' );

Next, the rewrite rule which will convert incoming requests to /download/resource/file/ from the pretty URL to query vars:

function wpa82328_rewrite_rule(){
add_action( 'init', 'wpa82328_rewrite_rule' );

This will point everything to the main index.php file with the resource and filename query vars set.

Next, we have to capture these requests early, before the main query is run and headers are sent. If we try to do this later, headers will already be sent and downloads won't work.

I've hooked the parse_request action, where a ref array of query vars is passed as an argument to the hooked function. We can then easily check for the presence of the wpa82328_file query var, which will tell us that a download is being requested. Read the comments within the code to understand what is happening in each step:

function wpa82328_parse_request( &$wp ){

    // if the wpa82328_file query var is set
    if ( array_key_exists( 'wpa82328_file', $wp->query_vars ) ){

        // query for the requested resource
        $args = array(
            'name' => $wp->query_vars['resource'],
            'post_type' => 'resource'
        $resource = new WP_Query( $args );

        // get all of the custom fields for this resource
        $custom_fields = get_post_custom( $resource->post->ID );

        // check each custom field for a value that matches the file query var
        foreach( $custom_fields as $key => $value ):

            // if a custom field value matches, we have the correct key
            if( in_array( $wp->query_vars['wpa82328_file'], $value ) ){

                echo 'file key is: ' . $key;

                // increment count
                // set proper headers
                // initiate file download


add_action( 'parse_request', 'wpa82328_parse_request' );

The last part you'll have to sort out is extracting the number from the key so you can get the correct count and asset keys. I would personally change it to have the number last, making it easier to just do a substr on the key to get the number. Or better, I would store them all as an array of arrays under a single key, so once you find the index that contains the requested name, you just have to go to the next index to find the count, and the next to find the asset url.

  • Thank you! This is exactly what I need! I think I would never find a proper solution like this. I did the increment count, set headers to initiate the download, however, I can't open the files that are downloaded this way... I guess I'm doing something wrong with the headers. Can you help me? $filename = 'http://site.com/assets/' . get_post_meta($resource->post->ID,'dllink1',true); header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($filename) . '"'); header('Content-Transfer-Encoding: binary'); readfile($filename); Commented Feb 1, 2013 at 14:40

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.