Programmatically Upload Files in WordPress (and Create Associated Meta Data)

This is post one of two on how to upload files in WordPress and created associated meta data.

In a recent project, I’ve been working on a plugin in which the user needs to upload a file to a custom post type by using an input element (of type file, obviously) rather than using the WordPress media uploader.

In addition to being able to upload the file, the user must also be able to view the file and remove the file (via Ajax) by an available anchor.

In order to do this, the plugin has to do the following:

  1. Upload the file to the uploads directory
  2. Save the file URL to the custom post type’s post meta data
  3. Save the file path to the custom post type’s post meta data
  4. Delete the file from the `uploads directory
  5. Clear the post meta data referencing the file

The primary reason that you have to store the file’s location on disk is because you can’t rely on PHP to delete a file via remote requests.

To that end, you need to be able to store not only where the file is on disk (for the sake of being able to delete), but also the URL of the file so that visitors or viewers can access the file via their browser.

Over the next two articles, I’ll share how to programmatically upload files in WordPress and save their associated meta data, and then I’ll share how to programmatically delete the files in WordPress as well as their associated meta data.

Programmatically Upload Files in WordPress

Note that I mentioned that we are not using the WordPress media uploader to do this; however, we need to be able to store the files in the uploads directory that exists in the users WordPress installation.

1. Define The Markup

This is where the wp_upload_bits function comes into play. But before we take a look at that code, we need some basic markup that allows the user to upload a file:

This snippet is obviously part of a larger display within the plugin, but it should be easy enough to follow. The most important thing is that you recognize theinput element’s name attribute, and that you properly setup your nonce value.

Next, you need to setup the code for uploading the file. Before actually doing any uploading, you need to make sure that files are actually set for upload, and, if so, that the user has permission and the proper nonce values are being sent across the wire for the given file.

To do this, I normally setup the following conditional:

As well as these helper functions:

Assuming both of these evaluate to TRUE, then I can actually upload the file. And this is where wp_upload_bits comes into play.

Here’s what the full code looks like:

At this point, we’re successfully saving the file to the most recent subdirectory of the uploads directory.

Granted, this particular block of code could include a bit more error checking, the reduction of muting of file_get_contents warnings, as well as replacing file_get_contents with wp_remote_get, but the example above – for all intents and purposes – serves its purpose in demonstrating how to do this, and covering each of the other additions would add unnecessary length to this [already lengthy] article.

Of course, now we need to associate the uploaded file with the custom post type (or even just the basic post or page, if that’s what you’re working with).

2. Save The File Meta Data

Of course, before you set any meta data, you want to make sure that the upload was successful. To do this, you can setup the following conditional:

How you opt to handle the case when it fails is up to you.

Next, we need to check to see if existing post meta data exists for the post because a user could be upload a file in place of another file. If that’s the case, we don’t want to leave residual files cluttering the uploads directory.

To that end, we need a unique meta key that we can use for the post meta API. In my case, I’m simply using attached-file-url and attached-file-path – one piece of meta data will be the URL to the file, the other, obviously, is the path to the file on the disk.

If a file already exists, then I need to remove it. To do this, I first check the post’s meta data. If something exists for the given meta key, then I’ll delete the file.

Next, I’ll update the post’s meta data with the URL to the file as well as the path to the file.

Now at this point, if you’re to look in the uploads directory, you’ll see the file that you uploaded:

File Upload

And if you take a peek at the database, you’ll see the post meta data associated with the given post:

Post Meta Database

What About Deleting The Files?

Obviously, deleting the files is the next step in the process.

Doing this is obviously more than a matter of removing the file from the file system – it’s also removing the information from the post meta data in the database, and updating the interface to reflect this change.

In the last post in the series, I’ll cover how to do that.


Hey Tom. Looks good.
But I’m curious to know if there’s a reason for not using:
and perhaps also, wp_delete_attachment()?

    Two very useful functions (so thanks for mentioning them!).

    This particular post is coming out of work that I’ve for a current project with which I’m not actually interested in attaching media to posts, so manually doing it the way this particular article outlines it stems from that.

    Someone else has also asked why I didn’t mention `wp_handle_upload` which is another handy function. In that particular case, I simply opted to go with `wp_upload_bits` – I don’t really prefer one of those functions over the other (unless there are major advantages of which I am unaware).

    Thanks for mentioning this, though as it’ll give future readers a variety of functions from which to choose based on the work they’re doing :).

      Cool Tom. Thanks.

      Off the top of my head, in the spirit of standing on the shoulders of giants, I’m thinking maybe you could use more of the native functions to check for file exists and such. And then just kinda trick out – out being the keyword – what’s necessary after the fact. Maybe?

      Mind you, I have some custom classes / methods because the WP native stuff wasn’t the right fit. So you could be in the same boat, yes? :)

      But that’s just me. It’s early (i.e., more coffee). I also didn’t read your post in nitty-gritty details so I could be off the mark. Sorry.

      p.s. Again, a personal opinion but I wouldn’t store the full URL. Just the uploads sub-folder and the file name. If for some crazy reason things get restructured (or even the site domain changed) the URLs would be invalid and would have to cleaned up, etc. I think? Again, more coffee.

        Off the top of my head, in the spirit of

        Yes – I’m a big, big fan of leveraging libraries, functions, etc., that already exist (generally standing on the shoulders of giants), and using functions such as `file_exists`, etc. are always handy.

        The gists in this post are from an initial pass of development, so there is refactoring that can (and will) be done, but the concepts largely remain the same, in my opinion.

        By that, I mean you still have to check if a file exists – how you do it is the more subjective area, though I will say that experience breeds default use of better code than what I’ve likely shared. I’m cool admitting that :).

        Mind you, I have some custom classes / methods because the WP native stuff wasn’t the right fit. So you could be in the same boat, yes? :)

        There are definitely projects in which that happens – there are also requirements that will have me doing things that, for, say, a public site (as mentioned in Brent’s comment) that I wouldn’t normally share; however, I can’t worry about sharing information and then each and every point of avoidance, you know? I’d never get through an article ;D.

        But you’re right – in this particular case, there are a few things I’m having to roll my own way.

        Without coffee, all of your stuff is astute, Mark :). Glad to have you sharing your thoughts.

It is NOT advisable under any circumstances to allow someone the ability to upload a file AND access it directly via their browser… you’re just begging for your site to get hacked.

If you are going to upload files you should hide their location by giving the file on the server some kind of randomly generated name, then storing this new name in the db along with the original filename. This allows you to reference the file by it’s original name in any UI you build, but use the file’s new secret random name when actually accessing it. I still wouldn’t under any circumstances, allow the person that uploaded the file to have access to it, other than maybe deleting it in order to replace it by uploading another file.

    I agree with you – this is web app security 101 and I should probably make a note about that in the post; however, this particular plugin is being built to be used behind a private firewall for an intranet and this is part of the requirements.

    Ah, and I should mention that this particular implementation allows for original files to be overwritten, as you’ve mentioned.

    Anyway, thanks, Brent, for mentioning this – now that it’s covered here, and I’ll likely update the post to link to this comment so people are aware of proper circumstances :).

      Hi Tom

      It’s me again :)

      I’d be interested in discussing Brent’s point with you as it could / should be applied to a WP-centric world. My gut feeling is much of what is being batted around – post article – can be rolled into a universal class. That is, roll it once (for all) and then it’s less thinking more doing :)

Hey tom – came across this whilst looking up methods of handling file uploads in WordPress – specifically handling the deleting of file already uploaded – very much looking forward to part 2 (if it isn’t up already? I couldn’t find it…!)

Once again, thanks for the great tutorial,


Hi there

I really enjoyed your tutorial as it was super easy to follow. Thanks very much indeed.

Thing is, though, it seems you forgot to complete it… or am I just totally missing something obvious? Quite new to WP so possible… but here is what I am reading:
“Now we just have to set the meta information but before we do that we need to check that the file actually uploaded. At this point you will notice the meta data”

I can’t see the code for actually writing the meta data… did you skip that or am I just missing it?

Sorry if this sounds stupid but if I knew this stuff I wouldn’t need the tutorial in the first place so please bear with me :D


    “Now we just have to set the meta information but before we do that we need to check that the file actually uploaded. At this point you will notice the meta data…”

    This is coming from this gist. Obviously to the TODO is a little misleading. All of the work happens in this particular part where you’re saving information about the file’s URL and location in the post’s meta data.

    You may be interested in this gist.

      Thanks for the feedback.

      My needs were actually slightly different from what you were doing in that I needed to upload files and track who uploaded them, not what post they were being added to. First step was figuring out how to upload files and that you did brilliantly and I currently use your code in my project so thanks very much in deed.

      The uploader tracking, was the second part of the question and it seemed you skipped that so I was well disappointed but, thanks to he head start I was able to guess the rest and I found that post meta functions and kicked myself as I knew of the add_user_meta functions but I thought somehow there was a single function you must call with file uploads to set 10 different things in 8 locations in 5 files all at once… Seems WP is far less complicated than I keep thinking it is.

      I ended up making a new table and storing my own info there as the meta tables didn’t seem to make sense. My goal was to let each user upload stuff, have a moderator validate it first (setting the status value in my table to ‘accepted’) and then show one page to all visitors but have the content be only that which was uploaded by the user who’s account was being viewed.

      Sort of like emulating a multisite with a normal website. I got it up and running so thanks again. Only question I have now is: was there a better way than creating my own table…?


plz help me, how to add upload file function in woocommerce checkout page.


Hello Tom! Thanks for the post!

So far, this article is the one that has helped me understand how to handle file uploads in WordPress. I work developing websites for clients who need to do some specific tasks.

I’ve spent a lot of time looking for an article like this one.

Let me ask you a question: I can’t find how to do this very same thing but using the media uploader instead. I have a custom post type that only has to have a pdf file attached to it. I want to upload it using the media uploader provided with WP. Like with a button that says “Add PDF” and the media uploader displays.

I imagine that WP has a function for that like “wp_editor();” but I can’t manage to find it. Do you have any literature that I can read?

Thanks man,
I’m sorry for my poor english

    So far, this article is the one that has helped me understand how to handle file uploads in WordPress. I work developing websites for clients who need to do some specific tasks.

    Awesome. I love hearing that – glad to help!

    I imagine that WP has a function for that like “wp_editor();” but I can’t manage to find it. Do you have any literature that I can read?

    The closest article I have to do something like that is here. It’s not 100% what you’re looking for, but should get you on the right path to doing this.

very nice topic. I was in need for this beacuse i am developing a plugin which can upload a file after click on repay, pay button.

Hi there
thanks for this tutorial, i am trying to implement it with dropzone.js uploader in a wordpress website and when i upload a file it returns a message “Server responded with 0 code” and no file is uploaded, any ideas?
I checked permissions in upload folder and i uploaded a dummy image from the backend and it worked.

Leave a Reply

Name and email address are required. Your email address will not be published.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>