Description
StaticQ Media is a complete image processing pipeline for WordPress. Upload once — StaticQ handles resizing, WebP conversion, cloud offloading, and front-end delivery in the background via WordPress cron. No external API. No per-image fees. Processing runs on your server, files live in your own cloud bucket.
How It Works
- Upload — image enters the processing queue
- WordPress cron generates all registered sizes + WebP variants
- Processed files are uploaded to your Cloudflare R2 bucket
- Front-end delivery rewrites
<img>tags to<picture>elements with CDN URLs and WebP sources
Processing
- Queue-based via WordPress cron — no upload slowdowns
- Configurable batch size and time budget per cron tick
- Automatic retry on failure
- Works on shared hosting — no memory spikes, no timeouts
- Cloudflare Image Resizing support (optional)
- WebP conversion with quality control
- EXIF metadata stripping for privacy
- Big image scaling control
- Non-image media support (PDFs, documents, etc.)
Storage
- Cloudflare R2
- Three modes: Local only / Local + Cloud / Cloud only
- Chunked uploads for large files
- Credentials via wp-config.php constants or settings page
Front-End Delivery
- HTML5
<picture>elements with WebP<source>fallbacks - Automatic srcset generation from registered image sizes
- Lazy loading with eager-first-image option
- Output buffer-based — works with any theme or page builder
- Per-class and per-size exclusions
Media Library Scanner
Change your image sizes, switch themes, enable WebP — your media library drifts out of sync. The Media Library Scanner realigns everything in one pass across local disk and cloud bucket.
- Metadata check — detects corrupted metadata, missing sizes, wrong dimensions, deprecated sizes, and missing WebP entries
- File check — verifies every file exists in the correct storage location and flags sync gaps between local and cloud
- One-click repair per attachment — fixes metadata first, then generates missing files, uploads, or downloads as needed
- Batch Fix All — resolve every issue across your entire library at once
- Retroactive registration — index your existing media library for processing after install
- Regenerate mode — force re-process all sizes from the original file
Post Content Scanner
Switched CDN providers, migrated domains, changed image sizes — your post content may still reference old URLs or outdated filenames. The Post Content Scanner inspects every post for stale image references baked into your database.
- Scans all posts, pages, and custom post types regardless of status
- Wrong domain detection — finds CDN or foreign domain URLs that should point to your local site
- Stale size references — detects image filenames that no longer match current attachment metadata
- Deleted attachment references — flags images linked to attachments that no longer exist (info only)
- Broken URL detection — identifies image URLs that cannot be resolved to any attachment (info only)
- One-click fix per post — rewrites wrong domain URLs and updates stale size references in post content
- Batch Fix All — resolve every fixable issue across all posts at once
- Table backup — back up wp_posts before applying fixes as an extra safety measure
Orphan Detection
Deleted posts, removed theme sizes, failed uploads — files pile up with no attachment record. The Orphan Detection tool finds them across local disk and cloud bucket.
- Scans local uploads directory and cloud bucket for files with no WordPress attachment
- Folder exclusions — skip known third-party directories to avoid false positives
- Quarantine — move orphans to an isolated folder for review before permanent deletion
- Restore from quarantine — bring files back if needed
- Export orphan list as CSV for external review
Run the Media Library Scanner first to fix metadata issues, then the Post Content Scanner to clean up stale references in your posts, then Orphan Detection to catch leftover files. Nothing is deleted without your review.
Cloudflare Worker Deployment
Deploy a Cloudflare Worker to serve media directly from your R2 bucket with automatic fallback to your origin server. The built-in wizard handles deployment, testing, and configuration.
Developer Configuration
Key wp-config.php constants for fine-tuning:
SQMEDIA_CRON_BATCH_SIZE— attachments per cron tick (default: 1)SQMEDIA_CRON_MAX_SECONDS— time budget per cron tick (default: 25)SQMEDIA_SCAN_BATCH_SIZE— attachments per scanner batch (default: 50)SQMEDIA_R2_ACCESS_KEY_ID— R2 API credentialsSQMEDIA_R2_SECRET_ACCESS_KEY— R2 API credentialsSQMEDIA_R2_BUCKET— R2 bucket nameSQMEDIA_R2_ACCOUNT_ID— Cloudflare account ID
External services
This plugin connects to the following third-party services when configured by the user. No data is sent to any external service unless the user explicitly enables and configures the corresponding feature.
Cloudflare R2 (Object Storage)
When the user configures Cloudflare R2 as their storage provider, the plugin uploads processed media files (resized images and WebP variants) to the user’s own Cloudflare R2 bucket. Files are uploaded, downloaded, and deleted via the S3-compatible API.
- Data sent: media files (images), bucket credentials (API token, account ID)
- When: during cron-based queue processing, scanner repair operations, and orphan quarantine/restore
- Service provider: Cloudflare, Inc.
- Terms of Service
- Privacy Policy
Cloudflare Image Resizing (optional)
When enabled, the plugin sends original images to Cloudflare’s Image Resizing API to generate resized and WebP variants. This requires the user’s domain to be proxied through Cloudflare. The resized images are stored locally or in the user’s bucket — they are not hosted by Cloudflare.
- Data sent: image files via Cloudflare-proxied URL
- When: during cron-based queue processing, when Cloudflare Image Resizing is enabled in settings
- Service provider: Cloudflare, Inc.
- Terms of Service
- Privacy Policy
Cloudflare Workers API (optional)
When the user deploys a Cloudflare Worker fallback via the plugin’s setup wizard, the plugin communicates with the Cloudflare API to create and configure the Worker script on the user’s own Cloudflare account.
- Data sent: Worker script source code, Cloudflare API token, account ID, zone ID
- When: only during the one-time Worker deployment wizard
- Service provider: Cloudflare, Inc.
- Terms of Service
- Privacy Policy
Screenshots

Media Library Scanner detects missing sizes, missing WebP variants, broken metadata, and storage drift across your library — with one-click repair per category. 
Drill into any attachment to see exactly which sizes, WebP variants, and storage locations need attention. 
Post Content Scanner finds stale image URLs baked into your posts — wrong domains, deleted attachments, broken references — and rewrites them safely. 
Orphan Detection walks your uploads directory and bucket for files that no longer belong to any attachment in your media library. Safe scan, then review before cleaning anything up. 
Review orphan files before touching anything. Exclude folders, quarantine the rest, restore with one click if needed. 
Choose how your media is stored: local only, cloud only, or both. Cloudflare R2 included free — no monthly fees. 
Configure WebP conversion, picture-tag delivery, and per-size handling. Every option works without a paid tier. 
One-click Cloudflare Worker deployment — no Wrangler CLI, no JavaScript. The wizard generates the script, deploys it, sets up routes, binds the R2 bucket, and verifies the result. 
Register your existing media library in one read-only pass. New uploads register automatically.
Installation
- Install and activate from Plugins > Add New, or upload the
staticq-mediafolder to/wp-content/plugins/. - Go to StaticQ > Settings.
Cloudflare R2 setup:
- Create an R2 bucket in the Cloudflare dashboard.
- Create an API token with R2 read/write permissions.
- Add credentials to wp-config.php (recommended) or the settings page.
- Set a custom domain or enable the R2.dev public URL.
- Click “Test Connection.”
After storage is configured:
- Use Media Manager > Register to index your existing media library.
- New uploads are registered automatically.
- Run the Media Library Scanner to detect and repair any issues.
FAQ
-
How does the processing queue work?
-
Images are not processed on upload. They enter a queue and are processed in batches by WordPress cron. Batch size and time budget are configurable. This avoids CPU spikes and timeouts, especially on shared hosting.
-
Do I need a Cloudflare account?
-
No — Cloudflare is optional. The plugin is optimized for Cloudflare workflows (R2 offload, Cloudflare Image Resizing, and the optional Worker fallback), but it runs fully on your own server without any cloud account. In local-only mode, StaticQ uses WordPress’s native image editor (GD or Imagick) for resizing and WebP generation, files stay in your uploads directory, and the Media Library Scanner, Post Content Scanner, and Orphan Detection all work as a pure audit-and-cleanup toolkit. Cloudflare comes in when you want offsite storage, edge-based image resizing, or CDN fallback. R2’s free tier (10 GB storage, 10 million reads/month) is generous for most WordPress sites if you decide to enable it.
-
Will it slow down uploads?
-
No. Processing is fully decoupled from the upload request. WordPress cron handles it in the background.
-
Can I process my existing media library?
-
Yes. The Media Manager registration tool indexes your entire existing library. Then run the Media Library Scanner to detect issues and repair them — missing sizes, WebP variants, and cloud sync gaps are all handled.
-
What happens if I deactivate the plugin?
-
Your original files remain untouched. Cloud copies stay in your bucket. Front-end delivery stops — WordPress falls back to local URLs. No data is deleted on deactivation.
-
Does it work with page builders?
-
Yes. Front-end delivery uses output buffering, so it works with any theme, page builder, or caching plugin that outputs standard HTML.
-
How do I monitor processing progress?
-
The Media Manager page shows queue status, processed counts, and per-attachment state. Scanner results show issue breakdowns by category.
-
What does the Media Library Scanner do?
-
It checks every attachment against your current settings in one pass. Metadata is validated for missing sizes, wrong dimensions, deprecated entries, and missing WebP variants. Files are verified across local disk and cloud bucket to ensure they exist in the correct storage location. Issues are categorized by type and severity. You can repair individual attachments with one click, or use Batch Fix All to resolve everything at once.
-
What does the Post Content Scanner do?
-
It inspects every post, page, and custom post type for image references baked into the content. It detects URLs pointing to wrong domains (e.g. old CDN or migration leftovers), stale size references that no longer match current attachment metadata, deleted attachments still referenced in content, and broken URLs that cannot be resolved. Wrong domain and stale size issues can be fixed automatically — the scanner rewrites the URLs directly in the database. Deleted attachment and broken URL issues are reported for manual review.
-
How does Orphan Detection work?
-
Orphan Detection lists all files in your local uploads directory and cloud bucket, then compares them against the WordPress database. Any file with no matching attachment record is flagged as an orphan. You can exclude specific folders to avoid false positives. Orphans can be quarantined for safe review before permanent deletion. Run the Media Library Scanner first to fix metadata issues, then Orphan Detection to catch leftover files.
-
Does it strip EXIF data?
-
Yes. EXIF metadata (GPS coordinates, camera info, timestamps) is stripped during processing. This reduces file size and removes potentially sensitive location data.
-
Is StaticQ useful if I don’t use cloud storage?
-
Absolutely. Cloud offloading is just one part of the pipeline. The Media Library Scanner audits your entire library and lets you repair everything in one pass. Orphan Detection cleans up leftover files. The Post Content Scanner fixes stale image references in your posts. And the queue-based processing prevents CPU spikes on upload — all without moving a single file off your server.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“StaticQ Media” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “StaticQ Media” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
3.3.4
- Fixed a post_content corruption bug in the
content_save_prefilter (revert_cdn_urls_in_content). In Worker mode (the default), when the CDN host equals the WordPress site host, the previous logic computed a CDN prefix without the/wp-content/uploads/path segment and then ranstr_replace(cdn_prefix, local_prefix, $content), which prepended/wp-content/uploads/to every internal URL in the post on save. This doubled the path on media URLs (/wp-content/uploads/wp-content/uploads/...) and injected the path on non-media internal links (/lm002/became/wp-content/uploads/lm002/). - Fixed a related URL-corruption bug in the output-time
encode_upload_url_pathsrewriter. The same too-short prefix computation also misled its foreign-domain detection and stripped the/wp-content/uploads/segment when rewriting legacy/foreign CDN URLs to the current CDN host. Visible on sites with legacy-domain URLs in content (e.g.static.example.com www.example.com) or already-rewritten URLs from a separate CDN host. - Both functions now route through a shared
compute_current_cdn_prefix()helper that correctly returns{cdn_base}/{r2_prefix}/in Direct mode and{cdn_base}/wp-content/uploads/(host-swapped if needed) in Worker mode. When the CDN URL prefix equals the local prefix, the filters short-circuit. - The original bug was introduced by the 2026-04-29 refactor that wired
force_prefix_in_urlthrough the URL rewriter and madeget_url_prefix()return empty in Worker mode; the two affected functions then started producing wrong results because they relied onget_url_prefix()to fill in the URL’s path segment, which only worked in Direct mode. Sites in Direct mode (force_prefix_in_url = true) were never affected.
3.3.3
- Applied late output escaping at every echo site in the scanner and post-content-scanner admin UI, replacing earlier “pre-escaped helper returns HTML” patterns with helpers that return data and inline
esc_attr()/esc_html()at the echo site (WP.org review hardening). - Refactored
render_pagination_buttons()to print directly with per-attribute escaping instead of building an HTML string and echoing it whole. - Hardened inline JSON data islands (
<script type="application/json">) withJSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOTflags. - Fixed Media Library Scanner “All” tab count: it now shows the number of distinct attachments with issues (matching the rendered table), rather than the sum of per-category counts (which double-counted attachments matching multiple categories).
- Cron pipeline now detects silent size- and WebP-generation failures (transient image-editor or Cloudflare errors that previously left records marked
completeddespite producing nothing) and converts them to a real failure status, allowing the existing retry logic to recover the attachment on subsequent cron ticks. - Registration no longer guesses
original_locationfrom the current Original File Handling setting; it leaves the field NULL until verified. This prevents the Orphan Scanner from flagging legacy originals as orphans on sites where the setting was changed at some point — the scanner’s NULL branch indexes both possible locations until the Media Library Scanner aligns the record to observed state. - AJAX nonces on the Media Manager page are now refreshed continuously via the WordPress Heartbeat API. A tab left open beyond the 24-hour nonce-expiry window no longer silently fails on actions; the nonce stays valid for the life of the user’s login session. A safety-net handler also surfaces a clear “session expired, please reload” message if the refresh ever fails (e.g., browser-throttled background tab).
3.3.2
- Fixed
Move to /originals/original file handling: the unscaled original is now actually moved towp-content/uploads/originals/<dir>/locally, mirroring the bucket placement. Previously only the bucket copy moved while the local copy stayed alongside the master. - Fixed Media Library Scanner contradictory report on original-image rows: the location column now shows presence at the intended folder only (no longer reports a stale legacy copy as “present”), and the misplaced badge label was clarified to “Original: stale copy in old folder.”
- Fixed scanner double-emit of “Original missing” + “Original misplaced” badges on the same file when the original was at a legacy location.
- Fixed
Undefined array key "status"PHP warning during quarantine restore. - Suppressed harmless
Table doesn't existerrors logged when the WordPress Plugin Check tool runs the plugin under its alternatewp_pc_table prefix. - Removed verbose per-file scanner diagnostics from
debug.log(kept once-per-scan summaries). - Quieted per-image cron pipeline debug logs by default; opt back in via
define('SQMEDIA_DEBUG_VERBOSE', true);inwp-config.phpwhen investigating cron behavior.
3.3.1
- Renamed remaining
STATICQ_*wp-config.php constants to theSQMEDIA_*prefix for project-wide naming consistency (SQMEDIA_R2_ACCOUNT_ID,SQMEDIA_R2_ACCESS_KEY_ID,SQMEDIA_R2_SECRET_ACCESS_KEY,SQMEDIA_R2_BUCKET,SQMEDIA_R2_PREFIX,SQMEDIA_CF_TOKEN,SQMEDIA_SKIP_DIRS). Update wp-config.php on upgrade. - Escaped CDN URLs injected into
<img>srcandsrcsetvia thethe_contentfilter and the frontend output buffer withesc_url(), hardening rendered output. - Refactored frontend output buffer to a plain
ob_start()/ob_get_clean()pair ontemplate_redirect/shutdown(replacing the callback-based buffer) for clearer pairing and added a buffer-level guard so foreign nested buffers cannot trigger our close. - Made the JS-template
ob_start()/ob_get_clean()pairing in the scanner UI renderer trivially auditable by capturing the buffer to a local variable before passing it towp_add_inline_script(). - Included the plugin’s top-level
composer.jsonin the WordPress.org distribution zip.
3.3.0
- Renamed internal identifiers from
sq_tosqmedia_to meet WordPress.org prefix uniqueness requirements. Pre-3.3 dev-build installs: settings reset on upgrade — reconfigure R2 credentials and re-run the Cloudflare Worker wizard. - Removed Google Cloud Storage support from the free plugin; R2 is now the only storage provider.
- Hardened JSON input sanitization in orphan quarantine AJAX handlers.
- Excluded vendor binaries and build scaffolding from the distribution zip.
3.2.0
- Added Post Content Scanner — detects wrong domain URLs, stale size references, deleted attachments, and broken URLs in post content.
- Cloudflare R2 is now the default storage provider.
- All features fully available — no restrictions.
- Added table backup option before applying scanner repairs.
3.1.0
- Added Cloudflare Worker deployment wizard.
- Improved
<picture>tag delivery with format fallbacks. - Added Media Library Scanner with disk and bucket cross-referencing.
- Added orphan file detection and cleanup.
- Removed debug logging for cleaner production output.
3.0.0
- Introduced queue-based processing architecture.
- Added Cloudflare R2 offloading.
- Added WebP conversion.
- Added Media Library Scanner.
