Home Forums General Issues Force an Image/File upload to a particular directory


Force an Image/File upload to a particular directory

  • When I add an Image or File field, is it possible to force it into a particular server folder, sub-folder of “uploads”?

    Lets say the Image field is for User avatar upload – how can I ensure they all go to “uploads/avatars”?

    (Bonus question: Can I forcibly rename those Image files, during saving, to be a variant of some other field names, eg. firstname-lastname.ext?)

  • I found some information about altering the upload file name

    Thanks. I wonder how I would ensure that only applies to the ACF field in question, rather than filters ALL WordPress file uploads?

    (FYI – It’s on user-edit.php. I’d like to rename the file being uploaded through an Image field – specifically, by getting the ID and then username of the user currently being edited… which is proving to be the next challenge).

    Given that that I already have ACF filters going to take the Image and change the destination directory (as per the other thread), I’m not certain how it should fit together.

  • ACF has it’s own function and hooks

    $errors = apply_filters( "acf/upload_prefilter/type={$field['type']}", $errors, $file, $field );
    $errors = apply_filters( "acf/upload_prefilter/name={$field['_name']}", $errors, $file, $field );
    $errors = apply_filters( "acf/upload_prefilter/key={$field['key']}", $errors, $file, $field );
    $errors = apply_filters( 'acf/upload_prefilter', $errors, $file, $field );

    Same as the wp hook but also passes the $field.

  • Not sure I know how to use that.

    Do you fire it out of a first function like you did with the other pair of them, in the other thread? (per that thread, I’m already using upload_prefilter to change the upload directory for this Image field; I also want to rename the file).

    I modified my working code but it doesn’t work for the renaming…

    add_filter('acf/upload_prefilter/key=field_6140a6da30a17', 'prefilter_avatar_upload');
    function prefilter_avatar_upload($errors) {
        // This one added, but doesn't work, unsure what to do
        add_filter('wp_handle_upload_prefilter', 'avatar_upload_rename' );
        // in this filter we add a WP filter that alters the upload path
        add_filter('upload_dir', 'modify_avatar_upload_dir');
        return $errors;
        // (Old) second filter
        function modify_avatar_upload_dir($uploads_avatars) {
            // here is where we later the path
            $uploads_avatars['path'] = $uploads_avatars['basedir'].'/users/avatars';
            $uploads_avatars['url'] = $uploads_avatars['baseurl'].'/users/avatars';
            // $uploads_avatars['subdir'] = 'avatars';
            return $uploads_avatars;
        // + Rename the file for saving
        function avatar_upload_rename( $file ) {
            $file['name'] = 'test-prefix-' . $file['name'];
            return $file;
  • good question. I don’t think that hook will work because you can’t change the file name, only add errors for custom validation.

    I would use the wp_handle_upload_prefilter hook.

    Take a look in the file …/plugins/advanced-custom-fields-pro/includes/api/api-helpers.php

    There is a function in there named get_source_field() that shows how ACF gets the field associated with the upload. I would do the same thing to determine if the upload is for an ACF field, what field, and then alter the file name if it’s the right field.

  • Interestingly, get_source_field() is in a different place in my version of ACF Pro – media.php – and it’s a <em>private</em> function (any way around that?)


    So, if I straight-up attempt to get the source field using that inside my WP upload pre-filter…

    add_filter('wp_handle_upload_prefilter', 'custom_upload_filter' );
    function custom_upload_filter( $file ) {
        // Attempt to get the ACF field
        $field = get_source_field();
        // Begin testing rename - not the finished article
        $file['name'] = 'and-everything-is-awesome-' . $file['name'];
        return $file;

    … then the Media uploader warns:

    “Post-processing of the image likely failed because the server is busy or does not have enough resources. Uploading a smaller image may help. Suggested maximum size is 2500 pixels.”


    But, if I ostensibly copy the function code into my filter function…

    add_filter('wp_handle_upload_prefilter', 'custom_upload_filter' );
    function custom_upload_filter( $file ) {
        $field = false;
        // Search for field key within available data.
        // Case 1) Media modal query.
        if ( isset( $_POST['query']['_acfuploader'] ) ) {
            $field_key = (string) $_POST['query']['_acfuploader'];
            // Case 2) Media modal upload.
        } elseif ( isset( $_POST['_acfuploader'] ) ) {
            $field_key = (string) $_POST['_acfuploader'];
        // Attempt to load field.
        // Note the <code>acf_get_field()</code> function will return false if not found.
        if ( isset( $field_key ) ) {
            $field = acf_get_field( $field_key );
        // TEST RENAME
        $file['name'] = $user_id . 'and-everything-is-awesome-' . $file['name'];
        return $file;

    … it successfully gets the field key at that point, and therefore supports getting the field, too.

    So, let me think, what logic should I be using here… ?

    * If this is an ACF field with the specified key (since wp_handle_upload_prefilter will otherwise fire on every upload),
    * Get the User ID for the User in the current Edit page
    * Get User object and then Username slug
    * Use that to recompose the filename, but retain the original extension

    If so, I’m now hitting a non-ACF snag actually obtaining the current user from user-edit.php

    ie. the following and another method

        // If is current user's profile (profile.php)
        if ( defined('IS_PROFILE_PAGE') && IS_PROFILE_PAGE ) {
            $user_id = get_current_user_id();
        // If is another user's profile page
        } elseif (! empty($_GET['user_id']) && is_numeric($_GET['user_id']) ) {
            $user_id = $_GET['user_id'];
        // Otherwise something is wrong.
        } else {
            die( 'No user id defined.' );

    … provoke “An error occurred in the upload. Please try again later.” in the upload window… maybe because, the browser is still on the same page, user_id is not available at the point the “Select Image” modal has appeared?? Hmm…

  • I suspect the error is because the fact that user id is not available and the die statement is causing the upload error.

    I would try to look into what is available in $_POST, although I don’t know exactly how you can do this because all of this is happening in an AJAX request (this is the reason your die statement is causing an error)

    You could try outputting the data to the error log

    ob_start(); print_r($_POST); error_log(ob_get_clean());

    You might find the user ID included in $_POST that is submitted for the AJAX request.

  • To update the record…


    The “Renaming” part in the following successfully renames a file to a username, retaining the file extension – but only if a valid user ID is supplied as $userid (hard-coded here as $userid=2)…

    // Filter entry, cf.
    add_filter('wp_handle_upload_prefilter', 'my_pre_upload', 2, 2);
    // param 1: filter
    // param 2: function to execute
    // param 3: priority, eg 10
    // param 4: number of arguments, eg 2
    // pass the $userid as argument to your function
    function my_pre_upload($file, $userid=2){
        // if no user specified, get $current_user
        $userid = $userid ?: get_current_user_id();
        $user = get_userdata( $userid );
        // Renaming,  cf.
        $info = pathinfo($file['name']);
        $ext  = empty($info['extension']) ? '' : '.' . $info['extension'];
        $name = basename($file['name'], $ext);
        $file['name'] = $user->user_login . $ext;
        // Return
        return $file;

    Therefore, the question becomes (still), at what point can I access user_id such that I can pass it through add_filter?


    So, I am experimenting with whether I can set the user ID as a variable at the time the ACF field is loaded…

    // Get and remember User ID when field is loaded??
    function user_avatar_filter( $field ) {
        $myuserid = $_GET['user_id'];
        echo '<pre>load_field: GET user_id is '.$myuserid.'</pre>';
        // Important: return the field
        return $field;
    add_filter('acf/load_field/key=field_6140a6da30a17', 'user_avatar_filter');

    For test, that code successfully echos out, to the ACF field’s part of the Edit User admin, the correct user ID based on whose Edit User page we are viewing.

    So, can that somehow be passed to my_pre_upload() as parameter $userid… ?

    I haven’t managed it, but I don’t know whether that’s down to a) my poor understanding of global/local variable context or b) that this still isn’t the way this works.

  • If I can find some time I will see if I can test what is submitted. My thought is that the current user ID must be passed as part of the ajax request because WP would check to ensure that the user is logged in and has access to the media library.

  • I was wrong, it is not in the ajax request, but the function get_current_user_id() is available during to use in your wp_handle_upload_prefilter filter.

  • Reading back over things I am confused about why you cannot get the user ID and alter the image file name based on the user.

  • Doesn’t get_current_user_id() get the ID of the logged-in user, as opposed to the ID of the users currently visible on an Edit User form? (I, as admin, can edit other users).

  • OK, I see, you might want to know what user is being edited. I will see what I can find figure out if anything.

  • I’ve been learning from Stackexchange.

    A. Allegedly, when the Media uploader Ajax appears, the user_id is no longer available. So there’s a solution (eventually arrived at) in calling the referrer at that point (wp_handle_upload_prefilter), then parsing out user_id, in order to fetch the user and username with which to rename the file. My working code does that.

    Still, I think I need a way to conditionalise that in two ways: i) only for uploads where the referrer is user-edit.php or profile.php, ii) only for uploads where the instigator is the ACF field with key field_6140a6da30a17. The latter one may get me back to square one – establishing a line of communication between the field group in admin and the media uploader box.

    B. The answer to my different question deals more specifically with adding parameters to an Ajax request. Don’t fully understand it yet, would need to take the time.

  • The code supplied for plupload_default_params was a little off. get_current_screen() returns an object.

    add_filter('plupload_default_params', function($params) {
      if (!function_exists('get_current_screen')) {
        return $params;
      $current_screen = get_current_screen();
      if ($current_screen->id == 'user-edit') {
        $params['user_id'] = $_GET['user_id'];
      } elseif ($current_screen->id = 'profile') {
        $params['user_id'] = get_current_user_id();
      return $params;

    With this $_POST[‘user_id’] is set correctly and is available during wp_handle_upload_prefilter

  • I know what you mean.
    But using this code weirdly results in in admin list content (eg. user list, post list, media list) being depopulated. Counts are still good, but all rendered invisible.

  • It may be some sort of conflict with Admin Columns Pro. Why is nothing ever easy?

    I think it’s the same for the other plupload_default_params code from StackExchange

  • Re: lists disappearing… maybe not related to ACP…

    I think the culprit is focused around…

    elseif ($current_screen->id = 'profile') {
              $params['user_id'] = get_current_user_id();

    (If I remove that, things don’t break).

    Assuming the plupload_default_params function is getting executed on all pages (why would it?), does it maybe need a final else condition that just return $params;?

  • Does changing the index name have any effect on the conflict? Try something that’s not likely to be already used

  • also, there is a typo in my code = instead of ‘==` in the if statement.

    add_filter('plupload_default_params', function($params) {
      if (!function_exists('get_current_screen')) {
        return $params;
      $current_screen = get_current_screen();
      if ($current_screen->id == 'user-edit') {
        $params['user_id'] = $_GET['user_id'];
      } elseif ($current_screen->id == 'profile') {
        $params['user_id'] = get_current_user_id();
      return $params;
  • Every instance?

        // 1. Pass variables like user_ID from user-edit.php to Media uploader
        add_filter('plupload_default_params', function($_GET) {
            if (!function_exists('get_current_screen')) {
              return $_GET;
            $current_screen = get_current_screen();
            if ($current_screen->id == 'user-edit') {
              $_GET['user_id'] = $_GET['user_id'];
            } elseif ($current_screen->id = 'profile') {
              $_GET['user_id'] = get_current_user_id();
            return $_GET;

    This results in “There has been a critical error on this website. Please check your site admin email inbox for instructions.” debug.log says: “Cannot re-assign auto-global variable _GET.”

    Changing _GET to _BLAH removes that error, but the lists are still wiped out.

  • No, you should not be returning $_GET

  • Your code with no = typo and reverting _GET to $params seems to work…

    add_filter('plupload_default_params', function($params) {
      if (!function_exists('get_current_screen')) {
        return $params;
      $current_screen = get_current_screen();
      if ($current_screen->id == 'user-edit') {
        $params['user_id'] = $_GET['user_id'];
      } elseif ($current_screen->id == 'profile') {
        $params['user_id'] = get_current_user_id();
      return $params;

    That is:

    a) The depopulated lists issue is gone.

    b) It still seems to successfully pass user_id to the uploader, allowing me to fetch the username with which to rename the file.


    Now, for a bonus…

    In my wp_handle_upload_prefilter function custom_upload_filter(), I should really be checking that the image upload in question is coming from the specific ACF Image field keyed field_6140a6da30a17…

    Do you happen to know if the AJAX $_POST in custom_upload_filter() already has access to any information about the instigating field in question, anything which would give away field_6140a6da30a17? (I see traces of it in the uploader’s HTML).

    Or would I somehow need, on user-edit.php, to a) check for its presence and b), if present, send it through the plupload_default_params function? (I presume I can stuff $params[] with multiple other values).

  • the field that the image is being uploaded for is already in $_POST

        [name] => digitalfantasyart03e.jpg
        [action] => upload-attachment
        [user_id] => 2
        [_wpnonce] => XXXXXXXXXXX
        [_acfuploader] => field_614490846481e
Viewing 25 posts - 1 through 25 (of 30 total)

You must be logged in to reply to this topic.