Offload Media Manual Upload
🎯 Goal: Upload the contents of uploads/
into the correct object prefix in your bucket (as set by AS3CF_OBJECT_PREFIX
), without nesting the uploads
directory itself.
- Launch Cyberduck and Connect to R2
-
Open Cyberduck.
-
Click Open Connection.
-
Choose Amazon S3.
-
Enter:
- Server:
https://<your-account>.r2.cloudflarestorage.com
- Access Key ID / Secret Access Key: From your Cloudflare R2 settings
- Path: Leave blank or use the bucket name depending on Cyberduck’s setup
- Server:
⚠️ If using a custom endpoint or Cloudflare’s compatibility mode, configure Cyberduck for S3 (Custom) and set the correct endpoint.
-
Navigate to the Bucket and Object Prefix Path
Once connected:
- Open your bucket.
- Navigate to the folder path that matches your
AS3CF_OBJECT_PREFIX
, e.g.:
object-prefix/
This is where WordPress expects media to be stored in object storage.
You can find this prefix in your
.env
file:AS3CF_OBJECT_PREFIX=object-prefix/
-
Upload Files (⚠️ Not the
uploads
Folder)- On your local machine, open the “ folder.
- Select all its contents (year-based folders like
2023/
,2024/
, etc. and any other files). - Drag & drop those contents directly into the
object-prefix/
folder in Cyberduck.
✅ DO NOT drag the entire
uploads
folder — this would nest everything incorrectly like:object-prefix/uploads/2024/...
Which breaks media URL resolution.
-
Confirm Upload
- Use Cyberduck’s remote browser to verify folder structure inside your bucket:
object-prefix/2023/ object-prefix/2024/ etc.
- Confirm that uploaded files match what’s in your local
/uploads
folder.
-
Add entries to
wp_as3cf_items
tableWithin your project directory, make a temporary file and copy snippet below (
offload.sql
). Do not commit this file- Replace
<region>
with s3 bucket region. - Replace
<bucket>
with s3 bucket name. - Replace all
<project_slug>
with your project slug.
Import using wp cli:
terminalwp db import offload.sql
Check
wp_as3cf_items
table entries to have the correct paths.offload.sqlINSERT IGNORE INTO wp_as3cf_items ( provider, region, bucket, path, original_path, is_private, source_type, source_id, source_path, original_source_path, extra_info, originator, is_verified ) SELECT 'aws', '<region>', '<bucket>', concat('<project_slug>/', pm.meta_value) AS path, concat('<project_slug>/', pm.meta_value) AS original_path, 0, 'media-library', p.id AS source_id, pm.meta_value AS source_path, pm.meta_value AS original_source_path, 'a:2:{s:13:"private_sizes";a:0:{}s:14:"private_prefix";s:0:"";}', 0, 1 FROM `wp_posts` p LEFT JOIN `wp_postmeta` pm ON pm.post_id = p.id AND pm.meta_key = '_wp_attached_file' WHERE p.post_type = 'attachment';
- Replace