5-Step Guide to Add File Upload in EE4 Registration Form

IN THIS ARTICLE

Quick Answer

Event Espresso 4 still has no native file upload field in 2026. You have two paths. For most organisers, the free Files Addon for Event Espresso 4 handles it in under 10 minutes, with admin-configurable file-type restrictions.

For custom workflows (non-image files, custom storage, per-event rules, compliance), build your own add-on using EE4’s hooks: AHEE__before_spco_whats_next_buttons to render the input, and FHEE__EE_SPCO_Reg_Step_Attendee_Information__save_registration_form_input to save it. The 5 steps below cover the custom build, with 2026 security and EE5-readiness updates.

Suppose you’re running a photography workshop. Registrants need to submit three sample photos to qualify. Or a medical conference that needs a scanned ID. Or a scholarship event needing a one-page essay.

EE4 handles tickets and payments beautifully, but the one thing it’s never shipped natively is a file upload field.

We at WisdmLabs first published this guide in 2017 for teams who had to build it themselves.

In 2026, the landscape has shifted: there’s now a maintained free add-on that solves 80% of cases, and the remaining 20% (custom, high-security, or non-standard workflows) still need the code-your-own route.

This update covers both, plus the security and migration checks that weren’t urgent a decade ago.

Note: If you haven’t done this before and have no clue about PHP, JS or WordPress coding, you better contact an Event Espresso Pro developer. (We’re on that list, just so you know :D)

ImageEs

Source

Heads up: Event Espresso 5 is now the current release.

If you’re planning a new site or have already upgraded, note that EE5 uses a refactored forms and checkout system, so the EE4 hooks used in this guide (AHEE__before_spco_whats_next_buttons and the EE4 SPCO filter) don’t carry over as-is.

The good news: EE4 is still supported in 2026 and still has no native upload field, so this guide remains current for EE4 installs, and the free Files Addon is tested up to EE 5.0.8p for image uploads on both versions.

For EE5-specific guidance on the custom-add-on path, jump to the What About Event Espresso 5? section further down.

The Two Paths in 2026

Before you write a single line of code, decide which path actually fits.

Scenario Recommended path
Image uploads only (photos, scans saved as image) Files Addon (free plugin)
Registration for small to mid-sized events Files Addon
Non-image files (PDF, DOC, ZIP, video) Custom add-on
Files must go to S3, Azure Blob, or private storage Custom add-on
Per-event or per-ticket-type upload rules Custom add-on
Virus scanning or approval workflow before accept Custom add-on
HIPAA / GDPR / accessibility audit trail required Custom add-on
You’re already planning to migrate to EE5 soon Defer until after migration

 

Two Paths to Take

Source

 

Option A: the free Files Addon (10-minute path)

The Files Addon for Event Espresso 4 is a third-party plugin listed in Event Espresso’s own support docs. It adds a “File Upload” question type directly inside EE’s question editor.

What it does well:

  • Admin-configurable allowed file types per question (default: jpeg, jpg, png, gif, bmp)
  • A filter to change the upload directory path
  • Works with the standard EE4 question group workflow
  • Tested up to WordPress 6.5.2 and Event Espresso 5.0.8p

Where it falls short:

  • Default is image-only; non-image extensions need manual config
  • No per-user state, virus scanning, or approval workflow
  • No native S3/cloud storage support

For a typical photography workshop or headshot collection, install it and you’re done. For anything beyond that, keep reading.

Option B: build your own add-on (when Option A isn’t enough)

This is where the original 5-step guide still applies. EE4’s hook system hasn’t changed: the same action and filter hooks documented here are still active in EE4 core in 2026. What has changed is how you harden the add-on against today’s threats and how you structure it for the eventual move to EE5.

 

If you’re unsure which path fits your setup—or your event workflow involves more than just file uploads—this is usually where teams bring in experienced WordPress developers to avoid rework later.

 

Step #1 Scaffold the Add-On Plugin

Create a new plugin folder inside wp-content/plugins/:


wdm-file-upload-ee4/

├── wdm-file-upload-ee4.php

├── js/

│   └── wdm-file-upload-ee4.js

└── uploads/            (optional local storage

In wdm-file-upload-ee4.php, open with a standard plugin header:

<?php

/**

* Plugin Name: WDM File Upload for Event Espresso 4

* Description: Adds a file upload field to EE4 registration forms.

* Version: 2.0.0

* Requires PHP: 7.4

* Requires at least: 6.0

* Tested up to: 6.5

* License: GPL-2.0-or-later

*/

 

if ( ! defined( ‘ABSPATH’ ) ) {

exit;

}

What changed from the original 2017 guide: Requires PHP: 7.4 (EE4 officially dropped PHP 5.6/7.0 support in 2023) and an early ABSPATH check (now a WordPress coding-standard requirement).

 

Step #2 Render the Upload Field in the Registration Form

EE4 fires AHEE__before_spco_whats_next_buttons during the Single Page Checkout (SPCO) process, right before the “Proceed to Next Step” buttons. That action hook accepts two arguments ($current_step, $next_step) and fires on both the attendee_information step and the payment_options step.

You want it on attendee_information only:

add_action(

‘AHEE__before_spco_whats_next_buttons’,

‘wdm_render_file_upload_field’,

10,

2

);

 

function wdm_render_file_upload_field( $current_step, $next_step ) {

if ( ‘attendee_information’ !== $current_step->slug() ) {

return;

}

?>

<div class=”wdm-file-upload-field”>

<label for=”wdm_file”>Upload supporting document</label>

<input

type=”file”

name=”wdm_file”

id=”wdm_file”

accept=”.pdf,.doc,.docx,.jpg,.png”

/>

<p class=”description”>Max 5 MB. PDF, DOC, JPG or PNG only.</p>

</div>

<?php

}


Set the parent <form> tag to accept files by adding enctype=”multipart/form-data” via the FHEE__EE_Form_Section_Proper__html_class__html_class_array filter or by using jQuery in Step 3.

 

Step #3 Attach Enctype and Client-Side Validation

Create js/wdm-file-upload-ee4.js and enqueue it on the SPCO page:

add_action( ‘wp_enqueue_scripts’, ‘wdm_enqueue_file_upload_js’ );

 

function wdm_enqueue_file_upload_js() {

if ( ! function_exists( ‘is_espresso_checkout’ ) || ! is_espresso_checkout() ) {

return;

}

wp_enqueue_script(

‘wdm-file-upload-ee4’,

plugin_dir_url( __FILE__ ) . ‘js/wdm-file-upload-ee4.js’,

array( ‘jquery’ ),

‘2.0.0’,

true

);

}


In the JS file, set the form enctype and validate file size / type on the client (defence in depth, not a substitute for server checks):

jQuery(function ($) {

var $form = $(‘form[id*=”ee-spco-attendee_information”]’);

if (!$form.length) return;

 

$form.attr(‘enctype’, ‘multipart/form-data’);

 

$form.on(‘submit’, function (e) {

var file = document.getElementById(‘wdm_file’).files[0];

if (!file) return; // optional field

var maxBytes = 5 * 1024 * 1024;

var allowed = [‘application/pdf’, ‘image/jpeg’, ‘image/png’,

‘application/msword’,

‘application/vnd.openxmlformats-officedocument.wordprocessingml.document’];

if (file.size > maxBytes) {

alert(‘File too large. Max 5 MB.’);

e.preventDefault();

} else if (allowed.indexOf(file.type) === -1) {

alert(‘File type not allowed.’);

e.preventDefault();

}

});

});


Step #4 Validate and Save the Upload on the Server

This is the step the original 2017 tutorial got roughly right but stopped short on security. In 2026 you cannot skip the hardening.

EE4 fires the filter FHEE__EE_SPCO_Reg_Step_Attendee_Information__save_registration_form_input when attendee info is saved. Hook into it:

add_filter(

‘FHEE__EE_SPCO_Reg_Step_Attendee_Information__save_registration_form_input’,

‘wdm_save_file_upload’,

10,

4

);

 

function wdm_save_file_upload( $save_status, $attendee_data, $registration, $reg_step ) {

if ( empty( $_FILES[‘wdm_file’] ) || empty( $_FILES[‘wdm_file’][‘name’] ) ) {

return $save_status;

}

 

require_once ABSPATH . ‘wp-admin/includes/file.php’;

require_once ABSPATH . ‘wp-admin/includes/image.php’;

 

// 1. MIME + extension validation (don’t trust $_FILES[‘type’])

$allowed = array(

‘pdf’  => ‘application/pdf’,

‘jpg’  => ‘image/jpeg’,

‘jpeg’ => ‘image/jpeg’,

‘png’  => ‘image/png’,

‘doc’  => ‘application/msword’,

‘docx’ => ‘application/vnd.openxmlformats-officedocument.wordprocessingml.document’,

);

 

$file = $_FILES[‘wdm_file’];

$check = wp_check_filetype_and_ext(

$file[‘tmp_name’],

$file[‘name’],

$allowed

);

 

if ( empty( $check[‘ext’] ) || empty( $check[‘type’] ) ) {

error_log( ‘WDM upload rejected: disallowed type for reg ‘ . $registration->ID() );

return $save_status;

}

 

// 2. Size check

if ( $file[‘size’] > 5 * 1024 * 1024 ) {

return $save_status;

}

 

// 3. Use wp_handle_upload (runs WP’s own sanitisation)

$overrides = array(

‘test_form’ => false,

‘mimes’     => $allowed,

);

$moved = wp_handle_upload( $file, $overrides );

 

if ( isset( $moved[‘error’] ) ) {

error_log( ‘WDM upload error: ‘ . $moved[‘error’] );

return $save_status;

}

 

// 4. Attach to the registration via EE’s extra meta

$registration->update_extra_meta(

‘wdm_uploaded_file’,

esc_url_raw( $moved[‘url’] )

);

 

return $save_status;

}


Four hardening notes worth repeating:

  • Never trust $_FILES[‘type’].Browsers let users spoof it. Use wp_check_filetype_and_ext(), which checks the actual file contents.
  • Use wp_handle_upload()rather than moving files by hand. It handles sanitised filenames, WordPress path constants, and hits WP’s own upload_mimes filter.
  • Store the link inside EE’s registration metavia update_extra_meta(). It shows up in the admin registration view automatically.
  • Log failures.error_log() entries are the only forensic trail when an upload fails silently for a user.

This is also the point where most DIY implementations break. File uploads touch security, storage, and validation layers simultaneously—something experienced WordPress developers account for early in the build, not after deployment.

Step #5 Harden, Test, and Ship

Before you deploy to production, run through this five-item checklist:

  1. Block PHP execution in your uploads folder. Add a .htaccess (Apache) or nginx config rule to deny .php, .phtml, .phar, and other executable extensions. WPForms’ 2025 file upload security guide lists this as the single most-skipped step in DIY upload code.
  2. Randomise filenames on upload. Use wp_unique_filename() or prepend a hash. Real attackers guess predictable paths.
  3. Validate on both client and server. The client-side check is for UX. The server-side check is for security. You need both.
  4. Test with logged-in and logged-out registrations. EE4 behaves slightly differently when a WordPress user is signed in during registration.
  5. Test on mobile. Safari and Chrome Mobile have subtle differences in how <input type=”file”> interacts with multi-step forms.

 

Further Reading: WordPress Development Setup Mistakes That Create Tech Debt

Hiring a WordPress developer vs. DIY: What Really Helps Your Business Grow?

How to Interview and Vet a WordPress Developer Before You Hire

WordPress VS Wix VS Weebly: Which one is better?

 

Frequently Asked Questions

Does Event Espresso 4 have a native file upload field yet?

No. As of April 2026, Event Espresso’s own support documentation still states that EE4 has no built-in file upload feature. The recommended path is either the free Files Addon on WordPress.org, or a custom add-on using the 5 steps in this guide.

Can I use this custom add-on approach on Event Espresso 5?

Not directly. EE5 has a different forms system and the hooks AHEE__before_spco_whats_next_buttons and FHEE__EE_SPCO_Reg_Step_Attendee_Information__save_registration_form_input are EE4-specific. If you’re on EE5, wait for the EE5 forms API documentation or use a general WordPress upload plugin like Gravity Forms with Event Espresso’s integrations.

What file types should I allow?

Start with the minimum your event actually needs. Images (jpg, png) for headshots and portfolios. PDF only for signed documents. Avoid accepting .zip, .exe, .php, .js, .html, or anything executable. Blocking by extension alone is not enough; validate the MIME type server-side with wp_check_filetype_and_ext().

Where should uploaded files live?

The cleanest pattern is a private directory outside the web root (for example, /var/www/wp-private-uploads/), accessed through a WordPress endpoint that checks the requester’s capabilities. Second-best is wp-content/uploads/ee4-registration-files/ with a .htaccess that denies direct access and serves files through admin-post.php. Never store uploads anywhere publicly indexable by default.

How do I add virus scanning to uploaded files?

The typical pattern is to run ClamAV on your server and invoke it from the FHEE__EE_SPCO_Reg_Step_Attendee_Information__save_registration_form_input filter on the server side. Cloud-based alternatives include VirusTotal’s API or attaching uploads to an S3 bucket with S3-native malware scanning. Rejecting the upload and alerting the admin on any positive scan is the minimum viable behaviour.

Can I let different events collect different file types?

Yes, but not with one-hook code. Read the event ID from the registration object inside the save filter and branch on event-specific settings stored in a custom options page or EE’s event meta. The Files Addon supports per-question type restrictions out of the box, which is often enough.

Is the free Files Addon maintained in 2026?

As of the latest WordPress.org listing, the plugin was tested up to WordPress 6.5.2 and Event Espresso 5.0.8p. It still works on current EE4, but check the WordPress.org page for the “Last updated” date before committing to it. A stale third-party plugin is a bigger risk than a fresh custom build.

Does Google’s AI search index Event Espresso registration pages?

Yes. Registration pages are regular WordPress pages and, unless you’ve deliberately blocked them, they’re crawled and cited just like any other content. The key is to keep the public-facing event description lean and schema-tagged with Event structured data. Upload fields themselves are behind the registration CTA and don’t need to be indexed.

If you need file uploads working inside Event Espresso—and you want it built securely, tested against real registrations, and ready for future upgrades—you’ll likely need more than just a plugin install.

This is where custom WordPress development comes in.

 

1. A quick call (30 minutes). We look at your EE setup, the file types involved, your compliance requirements, and tell you whether the Files Addon fits or you need a custom build.

2. A clear scope. Fixed-price where possible. We list every hook, every validation rule, every test case. Before anything starts.

3. We build it. On a staging site, against your actual event workflow. Security review baked in.

4. You review, we launch. Nothing goes live until a test registration with a real file lands in the admin view correctly.

5. You own it. Full code comments, readme, rollback notes. Our WordPress Bug Fixing Chatbot can help with post-launch issues as well.

 

Start with a free call →

6 Responses

    1. Hi Xander,
      The code provided is for EE4. EE3 and EE4 are coded differently, so yes, it won’t work with EE3.

  1. In what you’ve posted above, is the file just added to the media library, or is it connected to the user or registration in some way?

    This is so great – thanks for sharing this!

    1. Hi David,

      The above code just uploads the image to the media library, but you can use the below code to link it to the user (add this code on successful file upload):

                      //file is uploaded successfully. now insert record in table wp_posts and wp_postmeta
                      $wp_filetype = $movefile['type'];
                      $filename = $movefile['file'];
      
                      // Get the path to the upload directory.
                      $wp_upload_dir = wp_upload_dir();
                     
                      $attachment = array(
                                          'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ),
                                          'post_mime_type' => $wp_filetype,
                                          'post_title' => preg_replace('/\.[^.]+$/', '', basename($filename)),
                                          'post_content' => '',
                                          'post_status' => 'inherit'
                                      );
                      // Insert the attachment.
                      $attach_id = wp_insert_attachment( $attachment, $filename,$att_id);
      
                      // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it.
                      require_once( ABSPATH . 'wp-admin/includes/image.php' );
                       $p=$wp_upload_dir['subdir']. '/' .$filename; 
      
                      // Generate the metadata for the attachment, and update the database record.
                       // Define attachment metadata
                     $attach_data = wp_generate_attachment_metadata( $attach_id, $p );
                     
                     // Assign metadata to attachment
                     wp_update_attachment_metadata( $attach_id, $attach_data ); 
                     
                     //to assign metavalue for image_upload
                     update_post_meta($attach_id, 'wdm_custom_image_upload', 'yes'); 
                     
                      //set feature image for the post
                     set_post_thumbnail( $att_id, $attach_id );
                     
                   //echo image url
                     echo $attachment['guid'];
      
  2. Hi!

    I tryied your code and dont get it to work.. when i upload the picture it’s disipears …

    Do you have the full code how it looks like in the plugin and js file?

    Best regards!

    1. I have been trying to get the file upload to work with newest version of EE4 and i cannot get this plugin or the one that event Espresso advertises to do file uploads. Nothing seems to happen when choosing a file. I just get taken through to the success page but no file gets uploaded to the uploads directory.

      Is there something in WordPress 4.8 that breaks this function?

Leave a Reply

Your email address will not be published. Required fields are marked *