DataFirefly Image Optimizer — Shopware 6 Documentation
Installation, WebP/AVIF configuration, CDN integration, Twig API and troubleshooting for the Image Optimizer plugin for Shopware 6.6 and 6.7.
DataFirefly Image Optimizer automatically transforms every Shopware media image into WebP and AVIF variants, re-compresses original JPEGs and PNGs, and rewrites URLs to your CDN — without touching your theme. This documentation covers installation, full configuration, the Twig API exposed to themes, and troubleshooting.
Installation
The plugin ships as a ZIP. Two installation methods, functionally equivalent.
Via Shopware administration
- Settings → System → Extensions → Upload extension
- Select
DfImageOptimizer-1.0.0.zip - Click Install then Activate
- Clear cache: Settings → System → Cache & Indexes → Clear
Via CLI (recommended)
cd /path/to/shopware
unzip DfImageOptimizer-1.0.0.zip -d custom/plugins/
sudo -u www-data setsid php bin/console plugin:refresh
sudo -u www-data setsid php bin/console plugin:install --activate DfImageOptimizer
sudo -u www-data setsid php bin/console cache:clear
sudo -u www-data setsid php bin/console theme:compile
theme:compile is mandatory after installation so the Twig override for the thumbnail component becomes active on the storefront. Without it, <picture> tags won’t be generated even if WebP and AVIF files are produced.Post-install verification
Go to admin menu Catalogues → Image Optimizer. The dashboard should display with a Server capabilities card showing in real time:
- Detected PHP version (8.2 minimum)
- Imagick presence (recommended)
- GD presence (required)
- Effective WebP support — should be ✓
- Effective AVIF support — may be ✗ depending on server, not blocking
- Recommended engine: Imagick or GD
Architecture in two words
When an image is uploaded, the plugin listens to the entity media.written event, loads the image into a local temp file, then produces in parallel:
- The re-compressed original (replaces the file if Compress original is checked)
- A WebP sibling alongside with cumulative extension:
foo.jpg→foo.jpg.webp - An AVIF sibling alongside:
foo.jpg→foo.jpg.avif
Shopware thumbnails generated by the native ThumbnailService are processed the same way. On the storefront, the Twig override of storefront/component/image/thumbnail.html.twig wraps the <img> tag in a <picture> with AVIF, WebP and original-fallback sources — the browser automatically picks the lightest format it supports.
Configuration
Access: Settings → System → Extensions → DfImageOptimizer → Configure. Seven cards group the options.
“General” card
| Option | Default | Effect |
|---|---|---|
| Auto-optimize on upload | Enabled | Triggers the pipeline immediately on every upload. Disable if you’d rather process everything in the background via cron. |
| Process thumbnails | Enabled | Generates WebP/AVIF for Shopware thumbnails too (typically 4 to 6 sizes per source image). |
“WebP” card
| Option | Default | Recommendation |
|---|---|---|
| Enable WebP | Enabled | Keep enabled except in very specific cases. WebP is supported by 96% of browsers. |
| WebP quality (1-100) | 82 | 75-85 for good balance. 90+ for high-end photography, 70 for a heavy catalog. |
| Lossless for PNG | Disabled | Enable only if your PNGs contain text or sharp graphics (logos, icons). Otherwise lossy mode gives better gains. |
“AVIF” card
| Option | Default | Recommendation |
|---|---|---|
| Enable AVIF | Disabled | Enable if the dashboard indicates your server supports AVIF. Typical 50% gain vs JPEG, but encoding is slower than WebP. |
| AVIF quality (1-100) | 55 | 45-65 for excellent rendering. AVIF tolerates lower quality than JPEG/WebP thanks to its modern codec. |
| Maximum width for AVIF (px) | 2400 | CPU safeguard. Images above this width are skipped for AVIF but keep their WebP. Increase if you have a powerful server and need AVIF on large images. |
IMG_AVIF flag compiled in, or Imagick with libheif. The Server capabilities dashboard tells you exactly what’s available. If AVIF is not supported, the option stays inoperative even when checked — no error, just no AVIF generation.“Compression” card
| Option | Default | Recommendation |
|---|---|---|
| Compress original JPG/PNG | Enabled | Replaces the original with its re-compressed version. Irreversible — disable if you want to keep raw sources for future editing. |
| JPEG quality (1-100) | 85 | 85 is the web photo standard. Drop to 80 for extra gain if quality stays visually acceptable. |
| PNG compression level (0-9) | 7 | 9 = maximum compression but 3-4× slower. 7 is the standard balance. |
| Strip EXIF/ICC metadata | Enabled | Typical gain 5 to 30 KB per camera-output photo. Keep if you manage content needing precise color profiles. |
“CDN” card
| Option | Default | Explanation |
|---|---|---|
| Enable CDN URL rewriting | Disabled | If disabled, URLs point to your origin. Enable after configuring your CDN. |
| CDN base URL | — | Format: https://cdn.example.com with no trailing slash. E.g. https://shop-cdn.b-cdn.net for BunnyCDN. |
| Rewrite scope | Media only | See details below. |
| Preserve query strings | Enabled | Keeps cache-busting parameters (?v=1234) when rewriting. |
The three scopes in detail:
- Media only — rewrites only URLs starting with
/media/. Safest, covers 95% of typical use cases. - Media + thumbnails — adds
/thumbnail/. Useful if your storefront serves many dynamically generated thumbnails. - All static assets — adds
/theme/,/bundles/and/assets/. Only choose this if your CDN is correctly configured to pull-cache all assets and you’ve tested in staging.
“Frontend rendering” card
| Option | Default | Effect |
|---|---|---|
Output as <picture> tag |
Enabled | Wraps storefront <img> tags in a <picture> with AVIF/WebP sources. |
Add loading="lazy" |
Enabled | Native browser lazy-loading. Keep unless you have your own solution. |
Add decoding="async" |
Enabled | Lets the browser decode in parallel with HTML parsing. |
Force width/height |
Enabled | Anti-CLS (Cumulative Layout Shift). Browser reserves space before image loads. |
“Batch processing” card
| Option | Default | Recommendation |
|---|---|---|
| Batch size for cron task | 50 | 50 is a good balance. Increase to 100-200 if you need to catch up a large catalog quickly and your server can handle it. |
| Cron interval (minutes) | 15 | Information only — actual interval is defined by OptimizeImagesTask::getDefaultInterval(). To actually change, modify the value in the scheduled_task table or reinstall the plugin after change. |
Configure a CDN — concrete examples
BunnyCDN (recommended)
- Create a Pull Zone on bunny.net with your origin URL, e.g.
https://shop.example.com - BunnyCDN gives you a hostname like
shop-cdn.b-cdn.net - In the plugin config, set:
https://shop-cdn.b-cdn.net - Choose Media only scope to start
- Enable CDN rewriting
The plugin automatically injects <link rel="dns-prefetch" href="https://shop-cdn.b-cdn.net"> and <link rel="preconnect" href="https://shop-cdn.b-cdn.net" crossorigin> in the storefront <head> — saving 50 to 200 ms on the first CDN request.
Cloudflare
Cloudflare in standard DNS proxy mode doesn’t need CDN rewriting — Cloudflare automatically caches on your main hostname. But if you use a dedicated Cloudflare Custom Hostname for assets (e.g. cdn.example.com), configure it here. Also enable Cloudflare Cache Reserve or Polish to get Cloudflare optimization on top of yours.
KeyCDN
Same configuration as BunnyCDN: create a Pull Zone, get an URL like shop-12345.kxcdn.com, configure it in the plugin with the https:// prefix.
AWS CloudFront
Create a CloudFront distribution with your Shopware server as origin. Distribution URL is like https://d1234abc.cloudfront.net — or your custom domain if you’ve configured an alias. Set minimum TTL to 1 day to fully benefit from caching.
Detailed optimization pipeline
For each image (original or thumbnail) to process, the plugin runs these steps in order:
- Download source image from Shopware public filesystem to local temp file (
/tmp/dfimgopt_xxx.jpg) - If Compress original enabled: in-place re-compression with configured quality, metadata stripping if enabled. If the re-compressed version is smaller than the original, it replaces the source file on the filesystem.
- If WebP enabled: conversion to WebP, write sibling
foo.jpg.webpto public filesystem - If AVIF enabled and width ≤ max width: conversion to AVIF, write sibling
foo.jpg.avif - Record in
df_image_optimizertable with counters and bytes saved - Cleanup local temp file via
finallyblock (even on error)
Imagick is used by priority when available (superior quality and the only AVIF engine via libheif on many servers). GD takes over otherwise — it has supported WebP for a long time and AVIF since PHP 8.1.
Scheduled task — catching up existing images
Enabling the plugin on a shop already containing thousands of images doesn’t trigger retroactive optimization. This is intentional: converting 50,000 images to AVIF at once would saturate your server. Instead, the scheduled task df_image_optimizer.optimize_pending runs every 15 minutes by default:
- SQL query via LEFT JOIN on
df_image_optimizerto identify not-yet-optimized media - Process a batch of 50 images (configurable batch size)
- Finish and free the worker for the next task
On a shop with 10,000 images, expect about 50 hours to fully catch up in the background. To speed up:
- Increase batch size in config (try 100 or 200)
- Use the Run batch button on the dashboard several times in a row
- Run the task in a loop manually via CLI:
for i in {1..100}; do sudo -u www-data setsid php bin/console scheduled-task:run-single df_image_optimizer.optimize_pending; done
Twig API exposed to themes
Two Twig helpers are registered and usable in any theme or plugin template.
Filter |df_cdn
Rewrites a URL to the CDN if enabled, otherwise returns the URL unchanged. Useful for assets you include manually.
<img src="{{ media.url|df_cdn }}" alt="...">
<link rel="preload" as="image" href="{{ heroImage.url|df_cdn }}">
<style>
.hero { background-image: url("{{ bgImage.url|df_cdn }}"); }
</style>
Function df_picture()
Renders a full <picture> tag with AVIF, WebP and original-fallback sources, plus all configured attributes (lazy, async, width/height).
{{ df_picture(
media,
alt='Accessible description',
classes='product-image card-img',
sizes='(max-width: 768px) 100vw, 50vw'
) }}
Generates:
<picture>
<source type="image/avif"
srcset="https://cdn.example.com/media/foo.jpg.avif"
sizes="(max-width: 768px) 100vw, 50vw">
<source type="image/webp"
srcset="https://cdn.example.com/media/foo.jpg.webp"
sizes="(max-width: 768px) 100vw, 50vw">
<img src="https://cdn.example.com/media/foo.jpg"
alt="Accessible description"
class="product-image card-img"
sizes="(max-width: 768px) 100vw, 50vw"
loading="lazy"
decoding="async"
width="1200"
height="800">
</picture>
Admin API endpoints
Three REST endpoints are available, authenticated via the standard admin Bearer token.
| Method | Route | Description |
|---|---|---|
| GET | /api/_action/df-image-optimizer/stats |
Overview + 30-day activity + server capabilities |
| POST | /api/_action/df-image-optimizer/run-batch |
Runs a batch. Optional POST parameter batchSize (default 50, max 500) |
| GET | /api/_action/df-image-optimizer/capabilities |
Server detection (Imagick / GD / WebP / AVIF) |
curl example:
TOKEN=$(curl -s -X POST https://shop.example.com/api/oauth/token
-H "Content-Type: application/json"
-d '{"grant_type":"password","client_id":"administration","scope":"write","username":"admin","password":"shopware"}'
| jq -r .access_token)
curl -X POST https://shop.example.com/api/_action/df-image-optimizer/run-batch
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"batchSize":200}'
Tables created
df_image_optimizer
One row per optimized media. Unique key on media_id — re-optimization of the same media overwrites the row.
id BINARY(16) UUID
media_id BINARY(16) FK media.id ON DELETE CASCADE, UNIQUE
has_webp TINYINT(1)
has_avif TINYINT(1)
compressed TINYINT(1)
original_size BIGINT Original weight in bytes
bytes_saved BIGINT Cumulative savings (compression + WebP/AVIF delta)
sales_channel_id BINARY(16) FK sales_channel.id ON DELETE SET NULL
optimized_at DATETIME(3)
created_at DATETIME(3)
df_image_optimizer_log
Optional error journal. Read-only for debugging — no automatic purge.
Troubleshooting
“Dashboard shows AVIF: Not available”
Your server doesn’t have the required AVIF stack. Options:
- GD-only: check
php -m | grep gdandphp -i | grep AVIF. You need PHP 8.1+ and GD compiled with--with-avif. On recent Debian/Ubuntu, it’s default. - Imagick available: check
php -r "print_r(Imagick::queryFormats('AVIF'));". Empty? Your Imagick is not compiled with libheif — recompilation needed or switch to GD. - Acceptable fallback: leave AVIF disabled and focus on WebP. WebP-only gain is already huge compared to native Shopware JPEG.
“.webp files are generated but storefront still shows JPEG”
The theme compiler hasn’t picked up the Twig override. Fix:
sudo -u www-data setsid php bin/console theme:compile
sudo -u www-data setsid php bin/console cache:clear
Then verify with browser DevTools (Chrome or Firefox): open the Network tab, reload a product page, and look at MIME types of loaded images. You should see image/avif or image/webp instead of image/jpeg.
“Media upload has become slow”
AVIF conversion in particular is CPU-intensive — expect 1 to 3 seconds per image. If that’s annoying, disable auto-optimization on upload (General card) and let only the scheduled task process in background. Uploads become instant again and images are optimized within 15 minutes max.
“Thumbnail .webp files are not generated”
Verify Process thumbnails is checked in the General card. Then manually regenerate thumbnails so they go through the pipeline:
sudo -u www-data setsid php bin/console media:generate-thumbnails
“How do I clear all generated WebP/AVIF files?”
The plugin doesn’t delete them automatically, even on uninstall (to preserve your backups). To clean manually:
cd /path/to/shopware
find public/media -name "*.webp" -delete
find public/media -name "*.avif" -delete
“CDN URLs are not applied everywhere”
Check the configured scope. If you see origin URLs for theme assets (/theme/.../style.css), that’s normal with the default Media only scope. Switch to All static assets if your CDN is configured to serve all assets.
Note also that rewritten URLs concern server-side Twig rendering. If your frontend calls the Store-API and rebuilds URLs client-side in JS, you’ll need to apply rewriting on the client separately.
Uninstallation
sudo -u www-data setsid php bin/console plugin:uninstall DfImageOptimizer
sudo -u www-data setsid php bin/console plugin:remove DfImageOptimizer
On uninstall, Shopware asks whether to keep user data:
- Keep (default): the
df_image_optimizeranddf_image_optimizer_logtables stay in the database. Reinstalling the plugin resumes the history. - Don’t keep: both tables are dropped (DROP TABLE).
In both cases, .webp and .avif files on the filesystem remain — use the find commands above to clean them up if needed.
Going further
- Watch your Core Web Vitals score in Google Search Console — LCP should drop within 2 to 4 weeks after activation
- Test with PageSpeed Insights before/after — typical gain 20 to 40 points on mobile
- Also enable HTTP/2 or HTTP/3 server-side to multiply the CDN benefit
- Combine with a Shopware full-page cache for static response times