The current situation

We use the AWS SDK for PHP to store media files on S3 instead of the server's local storage, and use the wp_handle_upload filter to point the inserted attachment to the S3 file URL instead of the local file path and delete the local file as well.

function upload_to_s3( $upload ) {
    // MyS3Client, is a custom class that creates an S3Client object.
    $s3client = new MyS3Client();
    // myUploadMethod, is a custom method that stores the file on S3 and return its path.
    $s3_path  = $s3client->myUploadMethod( $upload );
    // Delete the local file.
    unlink( $upload['file'] );
    // Alter the file URL to point to S3.
    $upload['url']  = $s3_path;
    $upload['file'] = $s3_path;
    return $upload;
add_filter( 'wp_handle_upload', 'upload_to_s3' );

Something else, we don't generate any resized thumbnails from the original uploaded images; because we utilize the AWS Serverless Image Handler library to generate the needed sizes on the fly and serve them via CDN.

// Disable generating any resized thumbnails for the uploaded image.
add_action( 'intermediate_image_sizes_advanced', '__return_empty_array' );

So we insert these thumbnail sizes into DB manually; because WordPress uses some of them in the backend, like the images' thumbnails in the Media Library page, or the Media uploader form. At this point, we use the on-the-fly URL that will generate the thumbnails and store them on CDN.

function generate_cdn_thumbnails( $metadata, $attachment_id, $context ) {
    if ( $context === 'create') {
        $attachment_path   = parse_url( wp_get_attachment_url( $attachment_id ) )['path'];
        $metadata['sizes'] = [
            'thumbnail' => [
                'file'      => 'https://cdn.example.com/fit-in/150x150' . $attachment_path,
                'width'     => 150,
                'height'    => 150,
                'mime-type' => 'image/jpeg',
            'medium'    => [
                'file'      => 'https://cdn.example.com/fit-in/300x300' . $attachment_path,
                'width'     => 300,
                'height'    => 300,
                'mime-type' => 'image/jpeg',
            'large'     => [
                'file'      => 'https://cdn.example.com/fit-in/1024x1024' . $attachment_path,
                'width'     => 1024,
                'height'    => 1024,
                'mime-type' => 'image/jpeg',
    return $metadata;
add_filter( 'wp_generate_attachment_metadata', 'generate_cdn_thumbnails', 10, 3 );

The problems

1. Generate the metadata of the image before unlink it.

We know that WordPress can generate the metadata (width, height, filesize, image_meta, ...) of the local images only. So, either to generate them before unlink the local file, or download the uploaded image from S3 to the server to generate these data.

Currently we unlink the local file in the wp_handle_upload hook, so the file will be deleted before generating the metadata in the wp_generate_attachment_metadata hook.

Is there any better hook to use so we can unlink the file after generating its metadata? And how to pass the full local path of the file to that hook to use it in the unlink function?

2. Filter the resized CDN thumbnails URLs

In the generate_cdn_thumbnails function above, we store the full CDN URL of the thumbnails that will be generated on the fly. But when displaying these thumbnails in the backend (Media Library page, for example), WordPress prepends the folder path of the original image to that full URL.

Suppose we have this URL of the thumbnail size image:


The original (unlinked) image folder path https://www.example.com/wp-content/uploads/2022/07/18/ will be prepended to the thumbnail's full CDN URL, so the thumbnail will have this final URL (which is a broken URL) when displaying it on the Media page:


What is the proper hook to use to filter and fix the thumbnail URL before displaying it on the Media page?

  • 1
    Are you aware of Tachyon and S3 Uploads? Those have already solved these problems and may hold a solution for you (assuming you can't just use them as is). Also you should only ask 1 question per question. This isn't a discussion forum, so to write an answer you would need to answer both questions and be correct in answering both at the same time, so people are much less likely to answer. Whereas 2 questions asked separately is much easier and gets more views
    – Tom J Nowell
    Jul 19, 2022 at 2:37
  • can you explain why do you want to delete local files in the first place? Jul 19, 2022 at 2:43
  • Thank you @TomJNowell, we had used the S3 Uploads before, but we are trying to use the AWS SDK directly without the mean on any other plugins. For the Tachyon, it's the first time to hear about it, and it seems interesting, but we do other stuff besides the resizing, like the watermarking and image type conversion to webp. I think we may of with these plugins if we didn't get a solution. And for the 2 questions, I know you are right, but both are related to each other and have almost the same core reason. Appreciate your understanding and your kind reply! Thank you! Jul 19, 2022 at 10:59
  • @MarkKaplun, since we send the file to Amazon S3, we need to free up the server storage. We are a news channel website and our Media assets exceed 50TB, so we can't keep all of that on the local storage of the server. And we have multiple servers that run our website and are linked with a load balancer to distribute the traffic, so the file will be available on one of the servers only, while on S3, it will be accessible for all the servers. Jul 19, 2022 at 11:11
  • 1
    @JafarAbazeed that is good reason... have you tried to find S3 NFS adopter to use S3 as an external drive serverfault.com/questions/940827/…. Jul 19, 2022 at 13:19

1 Answer 1


Regarding the metadata portion of your question...I wonder if you could use the exif_read_data function?


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.