# Download Selected Media Files as a ZIP

> Adds a "Download as ZIP" bulk action to the Media Library list view that packs the selected original files into one archive, handy for sending a batch of photos or documents to someone.

- Type: PHP
- Runs: Admin only
- Category: [Media](https://wpide.com/library/category/media/)
- Tags: media-library, download, zip
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/media-download-selected-zip/

## Code

```php
// Largest total size to zip, in megabytes.
$wpide_zip_limit_mb = 200;

add_filter( 'bulk_actions-upload', function ( $actions ) {
    if ( class_exists( 'ZipArchive' ) ) {
        $actions['wpide_zip_media'] = 'Download as ZIP';
    }
    return $actions;
} );

add_filter( 'handle_bulk_actions-upload', function ( $redirect, $action, $ids ) use ( $wpide_zip_limit_mb ) {
    if ( 'wpide_zip_media' !== $action || ! class_exists( 'ZipArchive' ) || ! current_user_can( 'upload_files' ) ) {
        return $redirect;
    }

    $files = array();
    $total = 0;
    foreach ( $ids as $id ) {
        $path = function_exists( 'wp_get_original_image_path' ) && wp_attachment_is_image( $id ) ? wp_get_original_image_path( $id ) : get_attached_file( $id );
        if ( $path && is_readable( $path ) && current_user_can( 'read_post', $id ) ) {
            $files[] = $path;
            $total  += filesize( $path );
        }
    }

    if ( ! $files || $total > $wpide_zip_limit_mb * MB_IN_BYTES ) {
        return add_query_arg( 'wpide_zip', $files ? 'too_big' : 'empty', $redirect );
    }
    if ( defined( 'WP_CLI' ) && WP_CLI ) {
        return $redirect;
    }

    $zip_path = wp_tempnam( 'media.zip' );
    $zip      = new ZipArchive();
    if ( true !== $zip->open( $zip_path, ZipArchive::OVERWRITE ) ) {
        return add_query_arg( 'wpide_zip', 'failed', $redirect );
    }
    $used = array();
    foreach ( $files as $path ) {
        $name = wp_basename( $path );
        // Avoid collisions between files with the same name in different months.
        $name = isset( $used[ $name ] ) ? pathinfo( $name, PATHINFO_FILENAME ) . '-' . count( $used ) . '.' . pathinfo( $name, PATHINFO_EXTENSION ) : $name;
        $used[ $name ] = true;
        $zip->addFile( $path, $name );
    }
    $zip->close();

    nocache_headers();
    header( 'Content-Type: application/zip' );
    header( 'Content-Disposition: attachment; filename=media-' . gmdate( 'Y-m-d' ) . '.zip' );
    header( 'Content-Length: ' . filesize( $zip_path ) );
    readfile( $zip_path );
    wp_delete_file( $zip_path );
    exit;
}, 10, 3 );

add_action( 'admin_notices', function () use ( $wpide_zip_limit_mb ) {
    if ( empty( $_GET['wpide_zip'] ) ) {
        return;
    }
    $messages = array(
        'too_big' => sprintf( 'The selected files are larger than %d MB. Select fewer files.', $wpide_zip_limit_mb ),
        'empty'   => 'None of the selected files could be read.',
        'failed'  => 'The ZIP file could not be created.',
    );
    $key = sanitize_key( $_GET['wpide_zip'] );
    if ( isset( $messages[ $key ] ) ) {
        printf( '<div class="notice notice-error is-dismissible"><p>%s</p></div>', esc_html( $messages[ $key ] ) );
    }
} );
```

## Notes

Needs the PHP Zip extension; limited to 200 MB of files per download to protect the server.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Download Selected Media Files as a ZIP" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
