{"id":312313,"date":"2026-06-02T11:17:53","date_gmt":"2026-06-02T11:17:53","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/wrenio-media-offload\/"},"modified":"2026-06-02T11:17:18","modified_gmt":"2026-06-02T11:17:18","slug":"wrenio-media-offload","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/wrenio-media-offload\/","author":23498523,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"6.9.4","requires":"5.0","requires_php":"8.0","requires_plugins":null,"header_name":"Wrenio Media Offload","header_author":"Wrenio Studio","header_description":"Offload WordPress media files to Cloudflare R2 Storage with URL rewriting, bulk sync, and reconciliation.","assets_banners_color":"ccc9c6","last_updated":"2026-06-02 11:17:18","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/wrenio.studio\/wrenio-media-offload\/","header_author_uri":"https:\/\/wrenio.studio","rating":0,"author_block_rating":0,"active_installs":0,"downloads":34,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"wrenio","date":"2026-06-02 11:17:18"}},"upgrade_notice":{"1.0.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3557879,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3557879,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3557879,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3557879,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3557879,"resolution":"1","location":"assets","locale":"","width":2798,"height":1880},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3557879,"resolution":"2","location":"assets","locale":"","width":2798,"height":1880},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3557879,"resolution":"3","location":"assets","locale":"","width":2798,"height":1880},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3557879,"resolution":"4","location":"assets","locale":"","width":2798,"height":1880},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3557879,"resolution":"5","location":"assets","locale":"","width":2798,"height":1880},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3557879,"resolution":"6","location":"assets","locale":"","width":2798,"height":1880}},"screenshots":{"1":"Settings \u2014 General tab. Configure R2 credentials here, or (recommended) define them in <code>wp-config.php<\/code>.","2":"Settings \u2014 Features tab. Toggle URL rewriting, choose upload mode, enable delete-local-after-upload, and turn on debug logging.","3":"Bulk Sync \u2014 migrate existing media to R2 in batches. Shows totals and per-run options.","4":"R2 Reconcile \u2014 detects R2 object keys that have drifted from the file path WordPress expects, and previews the realignment in dry-run mode.","5":"Filename Cleanup \u2014 detects attachments where the local filename and the R2 key disagree.","6":"Orphan Cleanup \u2014 scans the bucket and identifies R2 objects no longer referenced by any attachment, with a reviewable preview list."}},"plugin_section":[],"plugin_tags":[3863,3882,84,46891,245358],"plugin_category":[50,59],"plugin_contributors":[78154,265450],"plugin_business_model":[],"class_list":["post-312313","plugin","type-plugin","status-publish","hentry","plugin_tags-cdn","plugin_tags-cloudflare","plugin_tags-media","plugin_tags-offload","plugin_tags-r2","plugin_category-media","plugin_category-utilities-and-tools","plugin_contributors-freemius","plugin_contributors-wrenio","plugin_committers-wrenio"],"banners":{"banner":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/banner-772x250.png?rev=3557879","banner_2x":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/banner-1544x500.png?rev=3557879","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/icon-128x128.png?rev=3557879","icon_2x":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/icon-256x256.png?rev=3557879","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-1.png?rev=3557879","caption":"Settings \u2014 General tab. Configure R2 credentials here, or (recommended) define them in <code>wp-config.php<\/code>."},{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-2.png?rev=3557879","caption":"Settings \u2014 Features tab. Toggle URL rewriting, choose upload mode, enable delete-local-after-upload, and turn on debug logging."},{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-3.png?rev=3557879","caption":"Bulk Sync \u2014 migrate existing media to R2 in batches. Shows totals and per-run options."},{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-4.png?rev=3557879","caption":"R2 Reconcile \u2014 detects R2 object keys that have drifted from the file path WordPress expects, and previews the realignment in dry-run mode."},{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-5.png?rev=3557879","caption":"Filename Cleanup \u2014 detects attachments where the local filename and the R2 key disagree."},{"src":"https:\/\/ps.w.org\/wrenio-media-offload\/assets\/screenshot-6.png?rev=3557879","caption":"Orphan Cleanup \u2014 scans the bucket and identifies R2 objects no longer referenced by any attachment, with a reviewable preview list."}],"raw_content":"<!--section=description-->\n<p>Move your WordPress media library to Cloudflare R2 \u2014 and serve every attachment from R2 (or a custom domain mapped to your bucket) without changing how WordPress works elsewhere.<\/p>\n\n<p><strong>What it does<\/strong><\/p>\n\n<ul>\n<li>Uploads new media to R2 as attachments are added (via scheduled background sync).<\/li>\n<li>Uploads existing media in batches via the Bulk Sync page.<\/li>\n<li>Rewrites attachment, <code>srcset<\/code>, and in-content image URLs to the R2 (or CDN) origin.<\/li>\n<li>Optionally deletes the local file after a successful upload to save disk inodes.<\/li>\n<li>Fills in thumbnail metadata for attachments whose local file has been deleted.<\/li>\n<li>Reconciles R2 object keys with local paths if they have drifted.<\/li>\n<li>Repairs filename-level mismatches between WordPress and R2.<\/li>\n<li>Identifies orphan R2 objects that are no longer referenced by any attachment.<\/li>\n<\/ul>\n\n<p><strong>Credentials<\/strong><\/p>\n\n<p>Credentials can be stored in the database via the settings page, or \u2014 recommended \u2014 defined as constants in <code>wp-config.php<\/code>:<\/p>\n\n<pre><code>define( 'WRENIO_R2_ACCOUNT_ID',        'your-account-id' );\ndefine( 'WRENIO_R2_ACCESS_KEY_ID',     'your-access-key' );\ndefine( 'WRENIO_R2_SECRET_ACCESS_KEY', 'your-secret-key' );\ndefine( 'WRENIO_R2_BUCKET_NAME',       'your-bucket-name' );\ndefine( 'WRENIO_R2_PUBLIC_URL',        'https:\/\/media.example.com' );\n<\/code><\/pre>\n\n<p>Constants take priority over values stored in the database.<\/p>\n\n<p><strong>The reconciliation tool<\/strong><\/p>\n\n<p>R2 object keys are derived from the stable <code>_wp_attached_file<\/code> meta. If keys drift away from an attachment's physical file path \u2014 for example, due to external tooling or imported content \u2014 the Reconcile page detects the drift and previews the realignment plan.<\/p>\n\n<p>Run <strong>Wrenio Media Offload \u2192 R2 Reconcile<\/strong> \u2192 \"Start Dry Run\" to scan and preview a fix plan. (Live realignment that moves R2 objects to their correct keys is available in the Pro version \u2014 see below.)<\/p>\n\n<p><strong>Cleanup tools<\/strong><\/p>\n\n<ul>\n<li><strong>Filename Cleanup<\/strong> detects attachments where the filename portion of <code>_wp_attached_file<\/code> differs from the R2 key (typically caused by double-dot or similar normalization mismatches) and repairs the WordPress meta to align with R2.<\/li>\n<li><strong>Orphan Cleanup<\/strong> walks the bucket via ListObjectsV2, builds the set of keys referenced in post meta, and offers a reviewable list of R2 objects that aren't referenced anywhere. Deletion is opt-in per batch.<\/li>\n<\/ul>\n\n<p><strong>Pro version<\/strong><\/p>\n\n<p>A Pro version is available at https:\/\/wrenio.studio\/ for users who need the destructive operations: live R2 reconciliation (moving drifted objects to their correct keys), orphan deletion (removing unreferenced R2 objects), and retry counter resets.<\/p>\n\n<h3>External Services<\/h3>\n\n<p>This plugin connects to the following external services. They are listed here so you know what data leaves your site and when.<\/p>\n\n<p><strong>Cloudflare R2 \u2014 required for the plugin to function.<\/strong><\/p>\n\n<p>You configure your own Cloudflare R2 credentials. The plugin uses them to upload, download, list, and delete objects in your bucket \u2014 only the bucket(s) you specify. No request is ever made to Cloudflare without your credentials, and the credentials never leave your server.<\/p>\n\n<ul>\n<li>What is sent: the media files you upload to WordPress, plus the API requests needed to manage those files in R2.<\/li>\n<li>When: on attachment upload, during bulk sync, and during reconciliation or orphan-cleanup runs you initiate.<\/li>\n<li>Cloudflare terms of service: https:\/\/www.cloudflare.com\/terms\/<\/li>\n<li>Cloudflare privacy policy: https:\/\/www.cloudflare.com\/privacypolicy\/<\/li>\n<\/ul>\n\n<p><strong>Freemius \u2014 used for plugin updates and Pro licensing.<\/strong><\/p>\n\n<p>This plugin uses the Freemius SDK to deliver plugin updates and (if you purchase a Pro license) to validate that license. By default, Freemius collects nothing from your site without your explicit opt-in. If you opt in at activation, anonymous usage statistics are shared to help improve the plugin; you can opt out at any time from the plugin's Account page.<\/p>\n\n<ul>\n<li>What is sent: only what you opt into when you first activate the plugin (or nothing at all if you skip the opt-in). When activating a Pro license, the license key is sent to Freemius for verification.<\/li>\n<li>When: at activation (one-time prompt), when a Pro license is activated or deactivated, and during update checks.<\/li>\n<li>Freemius terms of service: https:\/\/freemius.com\/terms\/<\/li>\n<li>Freemius privacy policy: https:\/\/freemius.com\/privacy\/<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>In WP admin \u2192 <strong>Plugins \u2192 Add New<\/strong>, search for <strong>Wrenio Media Offload<\/strong>.<\/li>\n<li>Click <strong>Install Now<\/strong>, then <strong>Activate<\/strong>.<\/li>\n<li>Go to <strong>Wrenio Studio \u2192 Wrenio Media Offload<\/strong>, open the <strong>Settings<\/strong> tab, and enter your Cloudflare R2 credentials. (Recommended: define them in <code>wp-config.php<\/code> instead \u2014 see the \"Credentials\" block in the Description above.)<\/li>\n<li>Click <strong>Test Connection<\/strong> to verify the bucket is reachable, then <strong>Test Write Access<\/strong> to verify your API token can PUT and DELETE.<\/li>\n<li>Enable <strong>URL Rewrite<\/strong> once both tests pass.<\/li>\n<li>Open the <strong>Bulk Sync<\/strong> page and run a bulk sync to migrate your existing media.<\/li>\n<li>Only after bulk sync reaches 100% and spot checks pass, consider enabling <strong>Delete Local Files<\/strong> under the Features tab.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"what%20happens%20to%20the%20local%20file%20when%20upload%20succeeds%3F\"><h3>What happens to the local file when upload succeeds?<\/h3><\/dt>\n<dd><p>By default, nothing. The local file stays on the server as a backup. Enable <strong>Delete Local Files<\/strong> under Features to delete after a successful upload \u2014 but only once you've fully verified your R2 copy.<\/p><\/dd>\n<dt id=\"what%20file%20types%20are%20allowed%3F\"><h3>What file types are allowed?<\/h3><\/dt>\n<dd><p>Images (jpeg, png, gif, webp, svg, bmp, tiff, avif), video (mp4, mov, avi, mpeg, webm, ogv), audio (mp3, ogg, wav, m4a, flac, aac), and PDF. Files up to 50 MB are supported.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20multi-site%3F\"><h3>Does it work with multi-site?<\/h3><\/dt>\n<dd><p>Yes, on a per-site basis. Each site stores its own settings and syncs to its own bucket.<\/p><\/dd>\n<dt id=\"what%20if%20reconciliation%20fails%20partway%20through%3F\"><h3>What if reconciliation fails partway through?<\/h3><\/dt>\n<dd><p>The tool processes attachments atomically: for each item, it copies, verifies, updates meta, then deletes the old objects. If any step fails, the attachment is left in a recoverable state \u2014 re-running reconciliation picks up where it left off. A retry cap (3 attempts per attachment) prevents a single bad item from blocking the rest; capped items appear in the Status table and are skipped on subsequent runs. (Resetting the retry counter to re-attempt a capped item is a Pro feature.)<\/p><\/dd>\n<dt id=\"is%20this%20safe%20to%20run%20on%20a%20production%20site%3F\"><h3>Is this safe to run on a production site?<\/h3><\/dt>\n<dd><p>The reconciliation and orphan-cleanup tools are destructive (Reconcile deletes old R2 objects after copying; Orphan Cleanup deletes objects it determines are unreferenced). Always:<\/p>\n\n<ol>\n<li>Take a database backup.<\/li>\n<li>Take (or accept loss of) an R2 bucket backup.<\/li>\n<li>Run in <strong>Dry Run<\/strong> mode first (Reconcile) or <strong>Scan + Preview<\/strong> first (Orphan Cleanup).<\/li>\n<li>Review the log output and\/or spot-check preview URLs.<\/li>\n<li>Only then run the live operation.<\/li>\n<\/ol><\/dd>\n<dt id=\"will%20error%20logs%20always%20be%20written%3F\"><h3>Will ERROR logs always be written?<\/h3><\/dt>\n<dd><p>Yes. ERROR and WARNING entries are always logged regardless of the Debug Logging setting. INFO and DEBUG require Debug Logging to be enabled.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"Offload WordPress media to Cloudflare R2 with URL rewriting, bulk sync, reconciliation, filename repair, and orphan cleanup.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/312313","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=312313"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/wrenio"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=312313"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=312313"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=312313"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=312313"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=312313"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=312313"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}