How-to-upload-files-to-the-custom-folder-in-WordPress
Home » BLOG » WordPress » How to upload files to the custom folder in WordPress

How to upload files to the custom folder in WordPress

category:  WordPress

I am recently working on a project that requires uploading the file to the custom folder. The default upload folder is still used for page, post, and custom post types. For the custom upload folder, WP users must be able to upload via the Media Library.

To achieve the request, I found the Custom Upload Folder plugin that meets the request. The code is simple and can be customized in the future. So I decided to share the code and explain it here. In this tutorial, I use the source code from the Custom Upload Folder plugin.

If your site uses the Advanced Custom Fields plugin and you use the file field type, this code will work with this file field type as well. Because the ACF file field type uses the native WP media popup.

How the Custom Upload Folder plugin works

All you have to do is to set the custom folder you want at “Settings>Media” and that’s it for the settings.

Custom Upload Folder settings

At the Media Library, you can now be able to choose the custom folder from the selection list that you set as shown below.

Upload file to custom folder via Media Library

At the Media Library, you can drag and drop the file or you can click on the “Select Files” button to upload the file (3). Or you can use the browser_uploader to upload the file (4). They all can upload the file to the custom folder you set. The custom folder will be created by the plugin.

After uploading, you can see the uploaded file including the folder name on the Media Library page. Plus, you can search for the filename and folder name from the search box as well. It is very handy.

View the uploaded file in Custom Upload Folder

Break Down the coding

The code is simple and just works as the plugin claims. Below is a class structure of the plugin.

class CustomUploadFolder 
{
   // default folders that set in "Settings>Media" page.
   private $default_folders = "assets\r\nassets/img\r\nassets/css\r\nassets/js";

   // A constructor allows you to initialize an object's properties upon creation of the object.
   // @link https://www.w3schools.com/php/php_oop_constructor.asp
   public function __construct() {
      // contains the action and filter hook
   ...
   }

   // change the WordPress upload folder to your custom folder
   public function upload_dir( $dirs ) {
   ...
   }

   // add new Custom Upload Folders settings page in the "Settings>Media" page
   function admin_init() {
   ...
   }

   // display a textarea field for enter the custom folders in the "Settings>Media" page
   function folders_input_callback() {
   ...
   }

   // display the selection list of the custom folders in the Media Library Add New page
   function custom_upload_folder_select() {
   ...
   }
}
new CustomUploadFolder;

That is all the code from the plugin. Let’s take a look at each method in the class.

Constructor method

This constructor is a place to add the action and filter hook.

Add the selection list of custom folders on Adding New Media page

Below the plugin adds the selection list of the custom folders on the “Adding New Media” page. It uses the “pre-upload-ui” action hook which fires before the upload interface is loaded.

// add the selection list of the custom folders on the "Adding New Media" page
// https://developer.wordpress.org/reference/hooks/pre-upload-ui/
add_action( 'pre-upload-ui', [ $this, 'custom_upload_folder_select' ] );
function custom_upload_folder_select() 
{
  	// get the custom folders from the "Settings>Media>Custom Upload Folder" page
	$folders  = explode( "\r\n", get_option( 'custom_upload_folders', $this->default_folders ) ); ?>

	<!-- display the selection list -->
	<?php _e( 'Select Upload Folder', __FILE__ ); ?>
	<!-- the selected custom folder will be stored in the cookie at the browser. so the plugin can use it when uploading the file to the selected custom folder -->
	<select class="js-custom-upload-folder" onchange="document.cookie='custom_upload_folder=' + event.target.value + ';path=<?php echo COOKIEPATH; ?>'">
		<option value="">
			<?php _e( 'Default', __FILE__ ); ?>
		</option>
		<?php foreach ( $folders as $folder )
		{
			$folder = trim( $folder );
			echo "<option value=\"{$folder}\">{$folder}</option>";
		} ?>
	</select>
	<img onload="
		var match = document.cookie.match( new RegExp( '(^| )custom_upload_folder=([^;]+)' ) );
		document.getElementsByClassName('js-custom-upload-folder')[0].value = match ? match[2] : '';
		"
src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" />
<?php 
}

Below is the result of the above code.

media_library_custom_folder

Add the folder column in the Media list table

Next, the plugin adds the Folder column in the Media list table and displays the custom folder in this Folder column.

// Media list table
// https://developer.wordpress.org/reference/hooks/manage_media_columns/
add_filter( 'manage_media_columns', function( $columns ) {
	// add the Folder column
	$columns['folder'] = 'Folder';
	return $columns;
} );
// https://developer.wordpress.org/reference/hooks/manage_media_custom_column/
add_action( 'manage_media_custom_column', function( $column_name, $post_ID ) {
	if( 'folder' == $column_name ) {
		$file = pathinfo( get_post_meta( $post_ID, '_wp_attached_file', true ) );
		echo $file['dirname'];
	}
}, 10, 2 );

Below is the result of the above code.

list_files_in_custom_folder

Add the Custom Upload Folder setting section in the “Settings>Media” page

The plugin adds the Custom Upload Folder settings section via the “admin_init” action hook.

// Add the Custom Upload Folder settings on the "Settings>Media" page
// https://developer.wordpress.org/reference/hooks/admin_init/
add_action( 'admin_init', [ $this, 'admin_init' ] );

From the admin_init callback, the plugin registers the setting via the register_setting function. It uses the “custom_upload_folders” as an option name that will store in the wp_options table. Meaning in coding, you can get the setting value by using get_option( ‘custom_upload_folders’).

Next, the plugin adds a new section and a new field. The new field is rendered by calling the “folders_input_callback” callback function.

function admin_init() {
	register_setting(
		'media',
		'custom_upload_folders', // the option name that we can get from the wp_options table ( get_opiont() )
		[
			'type' => 'string',
			'default' => $this->default_folders,
		]
	);

	add_settings_section(
		'custom_upload_folders_section',
		__( 'Custom Upload Folder', __FILE__ ),
		'',
		'media'
	);

	add_settings_field(
		'folders',
		__( 'Custom Upload Folders', __FILE__ ),
		array( $this, 'folders_input_callback' ),
		'media',
		'custom_upload_folders_section'
	);
}
function folders_input_callback() 
{ ?>
	<textarea id="custom_upload_folders" name="custom_upload_folders" class="regular-text ltr" rows="7"><?php echo esc_attr( get_option( 'custom_upload_folders' ) ); ?></textarea>
	<?php
}

Here is the result of the above code.

custom_upload_folder_settings

Upload the file to the custom folder

The last code is checking if the current page is from the media uploader. If yes, the plugin will change the default upload folder to the selected custom folder which the folder name is stored in the cookie in the browser. After uploading is completed, the plugin will change the default upload folder back to the WP default upload folder.

// Check if the current page is from the media uploader.
//
// $_SERVER['REQUEST_URI'] example:
// URI = http://www.example.com/some-dir/yourpage.php?q=apple&n=50000
// $_SERVER['REQUEST_URI'] will give you => /some-dir/yourpage.php?q=apple&n=50000
if ( preg_match( '/(async-upload|media-new)\.php/', $_SERVER['REQUEST_URI'] ) ) {
	// before upload       
	add_filter( 'wp_handle_upload_prefilter', function( $file ) {
		add_filter( 'upload_dir', [$this, 'upload_dir'] );

		return $file;
	} );

	// after upload
	add_filter( 'wp_handle_upload', function( $file ) {
		remove_filter( 'upload_dir', [$this, 'upload_dir'] );

		return $file;
	} );
}
public function upload_dir( $dirs ) {
	// get the selected custom folder from the cookie at the browser
	$folder_in_cookie = sanitize_text_field( filter_input( INPUT_COOKIE, 'custom_upload_folder' ) );

	if ( empty( $folder_in_cookie ) ) {
		return $dirs;
	}

	// get the custom folders from the "Settings>Media>Custom Upload Folder" setting
	$folders = explode( "\r\n", esc_attr( get_option( 'custom_upload_folders' ) ) );

	// check if the selected custom folder from the cookies is existing in the setting.
	// if yes, continue to change the default upload folder to the selected custom folder
	if ( in_array( $folder_in_cookie, $folders ) ) {
		$dirs['subdir'] = '/' . $folder_in_cookie;
		$dirs['path'] = $dirs['basedir'] . '/' . $folder_in_cookie;
		$dirs['url'] = $dirs['baseurl'] . '/' . $folder_in_cookie;
	}

	return $dirs;
}

The plugin uses the “wp_handle_upload_prefilter” filter hook to get the current file to upload then call the “upload_dir” filter hook in order to change the default upload folder to the selected custom folder.

Next, the plugin uses the “wp_handle_upload” filter hook to sanitize the file name, checks extensions for mime type, and moves the file to the custom folder within the uploads directory. After the uploading is completed, the plugin removes the “upload_dir” filter hook so the other files can upload to the WP default upload folder as normal.

Tips: Sometimes, you may work on a site where the theme or the plugin overrides the default upload folder as well. To force this above code to work on that site, you can add a priority number higher than the one that the existing theme or plugin uses. For example, if the existing theme or plugin uses the priority number 10, you can add any number that is higher than 10. Here is the priority explanation. Below is the sample code.

add_filter( 'wp_handle_upload_prefilter' , 'check_if_we_should_change_upload_dir', 20 );

And that’s it. The Custom Upload Folder plugin is simple and just works as per the requirement.

Full source code from the plugin

<?php
/*
Plugin Name: Custom Upload Folder
Description: Upload files to custom directory in WP Media Library.
Version: 1.1.2
Author: Motekar
Author URI: https://motekar.com/
Text Domain: custom-upload-folder
*/

class CustomUploadFolder
{
	private $default_folders = "assets\r\nassets/img\r\nassets/css\r\nassets/js";

	public function __construct() {
		// add the selection list of the custom folders in the "Adding New Media" page
		// https://developer.wordpress.org/reference/hooks/pre-upload-ui/
		add_action( 'pre-upload-ui', [ $this, 'custom_upload_folder_select' ] );


		// Media list table
		// https://developer.wordpress.org/reference/hooks/manage_media_columns/
		add_filter( 'manage_media_columns', function( $columns ) {
			// add the Folder column
			$columns['folder'] = 'Folder';
			return $columns;
		} );
		// https://developer.wordpress.org/reference/hooks/manage_media_custom_column/
		add_action( 'manage_media_custom_column', function( $column_name, $post_ID ) {
			if( 'folder' == $column_name ) {
				$file = pathinfo( get_post_meta( $post_ID, '_wp_attached_file', true ) );
				echo $file['dirname'];
			}
		}, 10, 2 );

		// Add the Custom Upload Folder settings on the "Settings>Media" page
		// https://developer.wordpress.org/reference/hooks/admin_init/
		add_action( 'admin_init', [ $this, 'admin_init' ] );


		// Check if the current page is from the media uploader.
		//
		// $_SERVER['REQUEST_URI'] example:
		// URI = http://www.example.com/some-dir/yourpage.php?q=apple&n=50000
		// $_SERVER['REQUEST_URI'] will give you => /some-dir/yourpage.php?q=apple&n=50000
		if ( preg_match( '/(async-upload|media-new)\.php/', $_SERVER['REQUEST_URI'] ) ) {
			// before upload
			// https://developer.wordpress.org/reference/hooks/wp_handle_upload_prefilter/
			add_filter( 'wp_handle_upload_prefilter', function( $file ) {
				// https://developer.wordpress.org/reference/hooks/upload_dir/
				add_filter( 'upload_dir', [$this, 'upload_dir'] );

				return $file;
			} );

			// after upload
			add_filter( 'wp_handle_upload', function( $file ) {
				remove_filter( 'upload_dir', [$this, 'upload_dir'] );

				return $file;
			} );
		}
	}

	public function upload_dir( $dirs ) {
		// get the selected custom folder from the cookie at the browser
		$folder_in_cookie = sanitize_text_field( filter_input( INPUT_COOKIE, 'custom_upload_folder' ) );

		if ( empty( $folder_in_cookie ) ) {
			return $dirs;
		}

		// get the custom folders from the "Settings>Media>Custom Upload Folder" setting
		$folders = explode( "\r\n", esc_attr( get_option( 'custom_upload_folders' ) ) );

		// check if the selected custom folder from the cookies is existing in the setting.
		// if yes, continue to change the default upload folder to the selected custom folder
		if ( in_array( $folder_in_cookie, $folders ) ) {
			$dirs['subdir'] = '/' . $folder_in_cookie;
			$dirs['path'] = $dirs['basedir'] . '/' . $folder_in_cookie;
			$dirs['url'] = $dirs['baseurl'] . '/' . $folder_in_cookie;
		}

		return $dirs;
	}

	function admin_init() {
		register_setting(
			'media',
			'custom_upload_folders', // the option name that we can get from the wp_options table ( get_opiont() )
			[
				'type' => 'string',
				'default' => $this->default_folders,
			]
		);

		add_settings_section(
			'custom_upload_folders_section',
			__( 'Custom Upload Folder', __FILE__ ),
			'',
			'media'
		);

		add_settings_field(
			'folders',
			__( 'Custom Upload Folders', __FILE__ ),
			array( $this, 'folders_input_callback' ),
			'media',
			'custom_upload_folders_section'
		);
	}

	function folders_input_callback() {
		?>
		<textarea id="custom_upload_folders" name="custom_upload_folders" class="regular-text ltr" rows="7"><?php echo esc_attr( get_option( 'custom_upload_folders' ) ); ?></textarea>
		<?php
	}

	function custom_upload_folder_select() {
		// get the custom folders from the "Settings>Media>Custom Upload Folder" page
		$folders  = explode( "\r\n", get_option( 'custom_upload_folders', $this->default_folders ) ); ?>

		<!-- display the selection list -->
		<?php _e( 'Select Upload Folder', __FILE__ ); ?>

		<!-- the selected custom folder will be stored in the cookie at the browser. so the plugin can use it when uploading the file to the selected custom folder -->
		<select class="js-custom-upload-folder" onchange="document.cookie='custom_upload_folder=' + event.target.value + ';path=<?php echo COOKIEPATH; ?>'">
			<option value="">
				<?php _e( 'Default', __FILE__ ); ?>
			</option>
			<?php foreach ( $folders as $folder )
			{
				$folder = trim( $folder );
				echo "<option value=\"{$folder}\">{$folder}</option>";
			} ?>
		</select>
		<img onload="
			     var match = document.cookie.match( new RegExp( '(^| )custom_upload_folder=([^;]+)' ) );
			     document.getElementsByClassName('js-custom-upload-folder')[0].value = match ? match[2] : '';
			     "
		     src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" />
	<?php }
}

new CustomUploadFolder;

Since the plugin doesn’t get updated for a while, you can copy the paste the code into the functions.php or create your own plugin. Then you can maintain the code by yourself. Since you understand the code, you can add any features you want in the future.

Hope you enjoy this post! If my post saves you time, please consider buying me a coffee to boost my energy to publish more useful posts.