# DataFirefly Image Optimizer — Shopware 6 Documentation

> 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.…

- Page: <https://www.datafirefly.com/en/documentation/dfimageoptimizer/>
- Language: en
- Last updated: 2026-06-29
- Other languages: [fr](https://www.datafirefly.com/documentation/dfimageoptimizer/index.md), [es](https://www.datafirefly.com/es/documentation/dfimageoptimizer/index.md), [de](https://www.datafirefly.com/de/documentation/dfimageoptimizer/index.md), [it](https://www.datafirefly.com/it/documentation/dfimageoptimizer/index.md), [pl](https://www.datafirefly.com/pl/documentation/dfimageoptimizer/index.md), [nl](https://www.datafirefly.com/nl/documentation/dfimageoptimizer/index.md), [pt](https://www.datafirefly.com/pt/documentation/dfimageoptimizer/index.md)
- Index: <https://www.datafirefly.com/en/documentation/llms.txt>

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

1. Settings → System → Extensions → Upload extension
2. Select `DfImageOptimizer-1.0.0.zip`
3. Click Install then Activate
4. 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
```

**Tip.** `theme:compile` is mandatory after installation so the Twig override for the thumbnail component becomes active on the storefront. Without it, `` 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 `` tag in a `` 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. |

**About AVIF.** AVIF encoding requires either PHP 8.1+ with the `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 `` tag | Enabled | Wraps storefront `` tags in a `` 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)

1. Create a _Pull Zone_ on bunny.net with your origin URL, e.g. `https://shop.example.com`
2. BunnyCDN gives you a hostname like `shop-cdn.b-cdn.net`
3. In the plugin config, set: `https://shop-cdn.b-cdn.net`
4. Choose _Media only_ scope to start
5. Enable CDN rewriting

The plugin automatically injects `` and `` in the storefront `` — 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:

1. Download source image from Shopware public filesystem to local temp file (`/tmp/dfimgopt_xxx.jpg`)
2. **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.
3. **If WebP enabled**: conversion to WebP, write sibling `foo.jpg.webp` to public filesystem
4. **If AVIF enabled and width ≤ max width**: conversion to AVIF, write sibling `foo.jpg.avif`
5. Record in `df_image_optimizer` table with counters and bytes saved
6. Cleanup local temp file via `finally` block (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.

**Original JPEG compression — irreversible.** When the _Compress original_ option is checked, the compressed version replaces the original on the filesystem. If you need to retrieve raw sources for other uses (printing, editing), disable this option — you'll still get gains via WebP and AVIF.

## 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:

1. SQL query via LEFT JOIN on `df_image_optimizer` to identify not-yet-optimized media
2. Process a batch of 50 images (configurable batch size)
3. 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 `` 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 gd` and `php -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_optimizer` and `df_image_optimizer_log` tables 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
