WP Manifestindependent plugin directory
manifest / media / media-to-s3

Media to S3

WordPress plugin: migrate and serve uploads via S3-compatible storage

by Cody Kaczynski · github.com/cxdy/media-to-s3 · website

0stars
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/cxdy/media-to-s3/releases/download/v1.0.0/media-to-s3-1.0.0.zip

Readme

Media to S3

Media to S3 — S3 bucket overflowing with media - generated with Google Gemini
logo generated by Google Gemini

Store WordPress media in S3-compatible object storage.
Same-host URLs or CDN · migrate from wp-admin or WP-CLI · new uploads follow

MIT OR GPL-2.0-or-later · WordPress 6.0+ · PHP 7.4+

Works with Amazon S3, Linode Object Storage, Cloudflare R2, MinIO, DigitalOcean Spaces, and any other S3-compatible API.

Documentation: https://cxdy.github.io/media-to-s3/
(source: Hugo site under docs/; guides for new sites and existing sites / migrate)

Requirements

  • PHP 7.4+ (8.x preferred)
  • WordPress 6.0+
  • For zip installs: no Composer on the host (vendor is bundled in release zips)
  • For development: Composer + PHPUnit
  • WP-CLI optional (admin migrate works without it)

Install

WordPress.org / release zip

  1. Download media-to-s3-x.y.z.zip from Releases (or WordPress.org when listed)
  2. Upload under Plugins → Add New → Upload Plugin
  3. Activate

From source

cd wp-content/plugins
git clone https://github.com/cxdy/media-to-s3.git media-to-s3
cd media-to-s3
composer install --no-dev --optimize-autoloader
wp plugin activate media-to-s3

Quick start

  1. Media → Media to S3
  2. Pick a provider preset (or Custom), enter bucket + keys
  3. Click Test (PUT/GET/DELETE), then Save Changes
  4. Enable the plugin
  5. Greenfield: done — new uploads offload live
    Existing library: open the Migrate tab (or use WP-CLI)

Step-by-step: new site · existing site

Prefer constants in wp-config.php (they override the options UI):

define('MEDIA_TO_S3_ENABLED', true);
define('MEDIA_TO_S3_ENDPOINT', 'https://us-ord-1.linodeobjects.com');
define('MEDIA_TO_S3_REGION', 'us-ord-1');
define('MEDIA_TO_S3_BUCKET', 'your-bucket');
define('MEDIA_TO_S3_ACCESS_KEY', getenv('MEDIA_TO_S3_ACCESS_KEY'));
define('MEDIA_TO_S3_SECRET_KEY', getenv('MEDIA_TO_S3_SECRET_KEY'));
define('MEDIA_TO_S3_DELETE_LOCAL', false); // default; keep locals until you choose otherwise
// Optional CDN:
// define('MEDIA_TO_S3_PUBLIC_BASE_URL', 'https://cdn.example.com/media');

Full variable list: Configuration.

Key layout

Local:  wp-content/uploads/2020/03/foo.jpg
Key:    2020/03/foo.jpg          (prefix empty)
URL:    https://site/wp-content/uploads/2020/03/foo.jpg

Optional MEDIA_TO_S3_PREFIX=media → key media/2020/03/foo.jpg (same-host URL unchanged unless public_base_url is set).

Same-host vs CDN

Mode Setting Browser URL
Same-host (default) public_base_url empty /wp-content/uploads/... on your domain; plugin streams from the bucket when the local file is missing
Public/CDN MEDIA_TO_S3_PUBLIC_BASE_URL set Attachment URLs rewrite to that base

Historic post HTML rewrite is optional, default off, and has a dry-run under Tools.

WP-CLI

wp s3 doctor
wp s3 migrate --dry-run
wp s3 migrate --resume
wp s3 migrate --path=2020/03 --resume
wp s3 migrate --engine=php --concurrency=3
wp s3 migrate --engine=s5cmd --numworkers=128
wp s3 verify --sample=100

Admin Migrate tab provides background jobs with progress UI (resume / pause / cancel) for shared hosting without WP-CLI.

Migration runbook: Existing site guide · Migration reference.

Media Library

  • List column: Offloaded / Local only / Local + remote / Missing remote
  • Attachment metabox: push to S3, delete local if remote OK
  • Bulk actions: push to S3, delete local when remote OK

How this differs from S3 Media Sync

Automattic’s S3 Media Sync solves a related problem—keeping WordPress media on Amazon S3—especially in VIP-oriented setups. This plugin is a separate project with a different audience and defaults.

S3 Media Sync (Automattic) This plugin (Media to S3)
Audience VIP / Automattic-oriented installs and tooling General WordPress: shared hosting, VPS, self-hosted
Storage AWS S3–focused Any S3-compatible API (Amazon S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Linode Object Storage, custom endpoints)
Public URLs Sync/upload to the bucket (VIP-style workflows) Same-host URLs by default (/wp-content/uploads/...); optional CDN / public base URL rewrite
Serving when local is gone Not the same product model Streams/proxies from the bucket when the local file is missing (same-host mode)
Migrate existing media WP-CLI–forward (wp s3-media …) Admin background job (progress, resume, pause, cancel) and WP-CLI (wp s3 migrate, optional s5cmd engine)
Delete local after upload Part of a tight S3 sync workflow Available, but defaults off for safety
Distribution GitHub / environment-specific install Release zips with vendor bundled (no Composer on the host); aimed at WordPress.org

If you already run VIP infrastructure and S3 Media Sync, stay there. If you want S3-compatible offload, same-host URLs, and migrate from wp-admin without VIP assumptions, this plugin is aimed at you.

Documentation

Operator docs are a Hugo site (GitHub Pages): https://cxdy.github.io/media-to-s3/

Doc Audience
Install Operators — zip / source install
Usage Operators — Test/Save, offload, Media Library
New site (greenfield) Operators — no migration
Existing site (migrate) Operators — migrate + cutover
Configuration Operators — constants, presets, secrets
Same-host vs CDN Operators — URL modes
Local development Contributors — Docker WordPress + MinIO
Architecture Contributors
# Preview docs locally
cd docs && hugo server

Development

composer install
composer test

Unit tests cover pure helpers (key mapping, settings precedence, URL rewrite, error classification, content dry-run) without a live WordPress install.

Local WordPress + MinIO (Docker)

Spin up WordPress, MySQL, and MinIO, seed dummy posts/media, and exercise migrate + live uploads:

composer install --no-dev --optimize-autoloader
./bin/local-up.sh
# Site: http://localhost:8080  (admin / admin)
# MinIO console: http://localhost:9001  (minioadmin / minioadmin)
docker compose --profile cli run --rm wpcli s3 doctor
docker compose --profile cli run --rm wpcli s3 migrate --engine=php --resume
docker compose --profile cli run --rm wpcli s3 verify

Full notes: Local development.

Build a release zip (bundles vendor, excludes tests, Docker, and the Hugo docs site). Output is versioned from the plugin header (Version:) as dist/media-to-s3-1.0.0.zip; the folder inside the zip stays media-to-s3/ for WordPress installs:

bash bin/build-release-zip.sh
# optional override: VERSION=1.0.1 bash bin/build-release-zip.sh

Privacy

This plugin does not phone home, collect analytics, or call author-operated servers.

License

Dual-licensed: MIT OR GPL-2.0-or-later — see LICENSE.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.0.0 Aug 6, 2026 media-to-s3-1.0.0.zip 0