Tutorial
File uploads on WordPress forms
File uploads are a paid add-on in most WordPress forms plugins. Here's why they shouldn't be, and the four things you need to get them right.
File uploads are the classic “premium” add-on in WordPress forms plugins. Pay $49/year, unlock the file upload field. The plugin authors will tell you it’s a complex feature.
It’s not. It’s an <input type="file">. The complexity is in the four things around it that the docs don’t tell you.
Here’s the full picture.
The basic case
The HTML is one line:
<p>
<label for="resume">Upload your resume (PDF)</label>
<input type="file" name="resume" id="resume" accept=".pdf,application/pdf" />
</p>
That’s the field. Core Forms handles the upload (multipart form-data, server-side reception, attachment-to-submission) automatically. The file is stored in the WordPress media library, the submission record links to it.
You don’t pay extra for this. It’s bundled.
The four things that matter
1. File size limits. WordPress’s php.ini controls upload_max_filesize and post_max_size. If a 5 MB upload fails, that’s why. Default on most hosts is 2 MB. Bump to 10-25 MB depending on use case.
2. Allowed MIME types. WordPress whitelists file extensions and MIME types. If you accept .docx uploads, you need to be sure WordPress’s whitelist includes it. (It does, but earlier WP versions didn’t.) Custom MIME types need a filter:
add_filter( 'upload_mimes', function( $mimes ) {
$mimes['indd'] = 'application/x-indesign';
return $mimes;
} );
For most use cases, the default whitelist (PDF, DOC/DOCX, JPG, PNG, MP4, ZIP) covers it.
3. Storage location. By default, uploaded files go into /wp-content/uploads/YYYY/MM/. Public, web-accessible. For job applications and confidential uploads, that’s the wrong place.
For sensitive uploads, route them to a private directory. Core Forms’ file upload settings have a “private” toggle that stores files in /wp-content/cf-uploads/ with .htaccess denying direct web access. Files are accessible to admin users via signed URLs.
4. Cleanup policy. Form uploads accumulate. After two years, a job-application form on a small site can have thousands of resumes in storage. Set a retention policy: 90 days, 1 year, whatever fits your privacy story. Core Forms can auto-delete old submissions and their attached files via the Submission limit + retention setting.
The security side nobody mentions
Three risks people overlook:
1. ZIP slip / path traversal. A malicious uploader sends a file named ../../wp-config.php. WordPress sanitizes filenames, but custom code that processes uploads needs to too. Use sanitize_file_name() if you handle uploads in a custom hook.
2. Polyglot files. A file that’s both a valid JPG and a PHP script. Browsers render it as JPG. Some old Apache configs would execute the PHP if the file ended up in a web-served directory. Modern WP + nginx: not a problem. Old shared hosting + Apache mod_php: still a thing. Always store uploads outside the document root or behind auth.
3. Disk fill attack. Bot uploads 1,000 ten-megabyte files. Disk fills, site goes down. Mitigation: rate-limit uploads (Core Forms’ submission rate limit helps), keep upload_max_filesize reasonable, monitor disk space.
These aren’t hypothetical. I’ve seen each of them on client audits.
The user experience side
Three patterns that improve UX:
1. Show the selected filename. Default <input type="file"> is ugly. Style it, show the chosen filename, give a “remove” button. Core Forms’ default theme handles this automatically — clicking the styled drop zone opens the file picker, the chosen file’s name renders below the field.
2. Validate file type client-side. Use the accept attribute. accept=".pdf,application/pdf" means the browser only shows PDFs in the file picker. Doesn’t replace server-side validation, but improves UX.
3. Validate file size client-side. A user trying to upload a 50 MB resume should see “too big” before the browser tries to upload it. The HTML5 File API exposes file.size. A 10-line JavaScript check saves the user the round-trip.
In Core Forms, both client-side type and size validation are built in. Set the limits in the form’s field settings; the validation fires on the file picker change event.
When uploads should not go through forms
Three cases where a forms plugin (any of them, including Core Forms) is the wrong tool:
1. Multi-gigabyte video uploads. WordPress wasn’t built for this. Use a chunked-upload service (Mux, Cloudinary, S3 multipart) and embed an upload widget.
2. Hundreds of files in one submission. Photo dump, asset bundle. The form metaphor breaks. Use a file-share tool (Dropbox File Request, Google Drive folder upload, Wormhole.app).
3. Files that need preview/edit before submit. Image cropping, PDF redaction, signature placement. These need a custom UI. Forms plugins don’t ship that.
For typical cases (PDF resume, JPG photo, ZIP code sample), forms-plugin uploads are fine.
The Core Forms specifics
In Core Forms, file uploads:
- Are bundled in every license (no add-on tax).
- Default to public uploads in
/wp-content/uploads/. Toggle “private” per-form for sensitive flows. - Get appended to the submission’s data record.
- Are accessible from the submissions admin, with one-click download per file.
- Auto-clean when the submission is deleted (via WP’s standard cleanup hooks).
The setup is one HTML field tag and a “private storage?” toggle.
The next step
If you’ve got a job-application form on a client site, audit it:
1. What's the max file size? (Should be 10-25 MB.)
2. Are uploads stored privately? (Should be yes for resumes.)
3. What's the retention policy? (Should be defined.)
4. Is there client-side validation? (Should match server-side.)
If any of those is “I don’t know,” fix it. The fix is one settings panel away in Core Forms.
Files uploads, like everything else, are bundled with the base license. No upgrade prompt.