# Symelo Image Optimizations user guide

For Symelo Image Optimizations version 1.0.0.

This guide explains how to set up Symelo Image Optimizations, what every option does, and how to fix problems yourself. The last section, "For developers", covers wp-config.php constants, hooks, WP-CLI commands and the REST API.

Need help? Write to office@symelo.com or visit [symelo.com](https://symelo.com/).

## Contents

1. [What it does](#what-it-does)
2. [Key terms](#key-terms)
3. [Requirements](#requirements)
4. [Installation and first setup](#installation-and-first-setup)
5. [Where to find everything](#where-to-find-everything)
6. [Overview tab](#overview-tab)
7. [Compression tab](#compression-tab)
8. [Delivery tab](#delivery-tab)
9. [Other Folders tab](#other-folders-tab)
10. [Engines tab](#engines-tab)
11. [Advanced tab](#advanced-tab)
12. [Support tab](#support-tab)
13. [In the media library](#in-the-media-library)
14. [How each file type is handled](#how-each-file-type-is-handled)
15. [How-to recipes](#how-to-recipes)
16. [Troubleshooting](#troubleshooting)
17. [Privacy and security](#privacy-and-security)
18. [Undoing and uninstalling](#undoing-and-uninstalling)
19. [For developers](#for-developers)

---

## What it does

Symelo Image Optimizations makes your images smaller without changing how your site looks. Everything happens on your own server; images are never uploaded to an outside service.

- **Compresses** JPEG and PNG files when you upload them and, with one click, across your existing media library.
- **Creates WebP and AVIF copies** of each image and serves them to browsers that support them.
- **Resizes** oversized uploads (for example 6000-pixel camera photos) to a maximum width and height.
- **Removes private metadata** such as GPS location and camera details.
- **Keeps the originals**, so you can restore one image or the whole library.
- **Lazy-loads** images and embeds, so the browser only downloads them when they're about to scroll into view.
- Improves **responsive images**, so phones don't download desktop-sized files.
- Optimizes images in **theme and plugin folders** too.
- Works with the image libraries your server's PHP has (Imagick and GD, which WordPress uses too) and shows exactly what is supported.

All work runs in the background. You can close the page while it runs.

## Key terms

| Term | Meaning |
|---|---|
| **Lossless** | Compression that keeps every pixel exactly the same. Savings come from better packing and removing metadata. |
| **Lossy** | Compression that drops detail the eye barely notices, for much smaller files. A *quality* number (for example 82 out of 100) controls how much. |
| **WebP** | A modern image format, typically 25–35% smaller than JPEG. Supported by all current browsers. |
| **AVIF** | A newer format, often half the size of JPEG. Slower to create. Browsers without AVIF support get WebP or the original. |
| **Copy** | The WebP or AVIF version the plugin saves next to an image, for example `photo.jpg.webp` and `photo.jpg.avif`. Your original file name and URL don't change. |
| **srcset / sizes** | HTML attributes that list the available sizes of an image (`srcset`) and how wide it's shown (`sizes`), so the browser can pick the smallest file that looks sharp. |
| **Lazy loading** | Loading an image only when the visitor scrolls near it. |
| **Engine** | The software that does the work: PHP's image libraries, Imagick and GD. |
| **CDN** | A content delivery network (such as Cloudflare) that caches your site's files on servers around the world. |

## Requirements

- WordPress 6.4 or newer.
- PHP 7.4 or newer. On older PHP the plugin stays inactive and shows a notice.
- At least one PHP image library: **Imagick** or **GD**. Without either, a notice says "this server has no image library (Imagick or GD), so images can't be optimized."
- Write permission for the web server on your uploads folder (and on any theme or plugin folders you choose to optimize).
- Optional: for WebP/AVIF through **Server rules**, Apache or LiteSpeed with mod_rewrite (and mod_headers), or access to your Nginx configuration. The default **&lt;picture&gt; tags** delivery works on every server.

Which formats your server can create depends on how PHP and its libraries were built. The **Engines** tab and the **Overview** tab show exactly what is available, and options that aren't possible are greyed out with the reason.

PDF compression isn't available with Imagick and GD, so **PDF compression** shows **No** and **Optimize PDF files** is greyed out.

Only administrators can open the settings (the `manage_options` capability). In the media library, anyone who can edit an image can optimize or restore it.

Multisite works. Each site has its own settings and records.

---

## Installation and first setup

### What happens on activation

1. In wp-admin, go to **Plugins → Add New → Upload Plugin**, choose the .zip file, click **Install Now**, then **Activate**.
2. The plugin creates its database table and schedules a daily maintenance task. Nothing is downloaded and no existing image is changed.
3. From now on, **new uploads are optimized automatically** a few seconds after you upload them, with the default settings: **Balanced** compression, WebP and AVIF copies, resizing to at most 2560 pixels wide, metadata removal, and originals kept.
4. Your existing images stay as they are until you start a bulk optimization.

The plugin's screen is under **Media → Image Optimizations**.

### Step 1: Check what your server can do

1. Go to **Media → Image Optimizations**. The **Overview** tab opens.
2. Look at **What this server can do**. Each job shows **Yes** with the engine that will do it, or **No**:
   **JPEG compression**, **PNG compression**, **WebP copies**, **AVIF copies**, **PDF compression**, **Resizing**.
3. If **WebP copies** or **AVIF copies** says **No**, open the **Engines** tab to see why, and ask your host to enable WebP/AVIF in Imagick or GD (see [WebP or AVIF isn't available](#webp-or-avif-isnt-available)). **PDF compression** says **No**: Imagick and GD can't compress PDFs.

### Step 2: Review the compression settings

The defaults suit most sites. If you want to check them, open the **Compression** tab. The most important choice is the **Compression level**; **Balanced** is recommended. See [Compression tab](#compression-tab).

### Step 3: Optimize your existing images

1. On the **Overview** tab, find **Bulk optimization**.
2. Click **Optimize all images**.
3. The progress bar shows "Finding images…", then "*X* of *Y* files". You can close the page; the work continues in the background.
4. When it's done, the card says "Last bulk run finished on …" and the top of the page shows your savings: "*N* media files processed. Visitors download *P*% less (*size*)."

On a small or shared server, set **Advanced → Background speed** to **Gentle** first. See [Use the plugin on a small server](#use-the-plugin-on-a-small-server).

### Step 4: Check that WebP and AVIF are served

1. Open the **Delivery** tab. **&lt;picture&gt; tags (recommended)** is selected by default and works everywhere.
2. Under **Check delivery**, click **Test now**. You should see "&lt;picture&gt; delivery is on: pages offer WEBP, AVIF versions of optimized images to browsers that support them.", followed by what the test found on your home page, for example "On the home page, 12 images have WebP/AVIF sources."

---

## Where to find everything

**Media → Image Optimizations** has eight tabs. They switch instantly without reloading.

| Tab | What it's for |
|---|---|
| **Overview** | Savings, bulk optimization, what the server supports, and recent problems. |
| **Compression** | Compression level, which copies to create, resizing, metadata, originals, excluded sizes. |
| **Delivery** | How WebP/AVIF are served, lazy loading and responsive images, with a delivery test. |
| **Other Folders** | Optimize images in your theme, plugins and other folders inside wp-content. |
| **Engines** | Which engines were found and which one does each job. |
| **Advanced** | Background processing status and speed, uninstall behaviour, and restoring everything. |
| **Guide** | This user guide, inside the plugin, with a contents list and a search box ("Find in this guide"). |
| **Support** | Help links, a system information report and contact details. |

The media library also gets an **Optimization** column and buttons, see [In the media library](#in-the-media-library). The Plugins screen has a **Settings** link under the plugin.

---

## Overview tab

### Summary

The top card shows one of:

- "*N* media files processed. Visitors download *P*% less (*size*)." Savings count what visitors actually download: the smallest of the file and its WebP/AVIF copies.
- "Ready. New uploads are optimized automatically; existing images can be optimized in bulk below."
- "This server has no engine that can compress images. See the Engines tab."

Warnings about outdated image libraries on your server appear below it (see [Image library warnings](#image-library-warnings)).

### Statistics

| Figure | Meaning |
|---|---|
| **Smaller for visitors (with WebP/AVIF)** | How much less visitors download compared with your original files. |
| **Disk space saved** | How much smaller the optimized files are than the originals (not counting copies). |
| **Files optimized** | Files that were made smaller or got copies. Each image size counts as one file. |
| **WebP copies** / **AVIF copies** | How many copies exist. |

These figures cover the media library. Folder images have their own figures on the **Other Folders** tab.

### Bulk optimization

Optimizes the images already in your media library with the current settings.

| Option | What it does | Default |
|---|---|---|
| **Also redo images optimized with different settings** | Re-optimizes images that were processed with another compression level, other copies, other resize limits or another metadata setting. Without it, only images that haven't been optimized (or failed, or were restored, or were replaced since) are processed. | Off |
| **Include the selected folders (themes, plugins)** | Also processes the images in the folders chosen on the **Other Folders** tab. Only shown when folders are selected. | On |

Click **Optimize all images**. The run has two phases:

1. **Finding images…**: the media library is scanned in small batches. If you turned on breakpoints (see [Responsive images](#responsive-images)), missing breakpoint sizes are created now.
2. **"*X* of *Y* files"**: the files are optimized.

While it runs you can **Pause**, **Resume** or **Cancel**. While paused, new uploads are still optimized. **Cancel** stops the run: "Files already optimized stay optimized."

The line "Speed: Normal (change)" shows the current background speed and links to the **Advanced** tab.

You don't need to keep the page open. While it *is* open, the page also helps the background work along.

### What this server can do

For each job (**JPEG compression**, **PNG compression**, **WebP copies**, **AVIF copies**, **PDF compression**, **Resizing**): **Yes** with the engine that will be used, or **No**. **Engines →** opens the **Engines** tab.

### Recent problems

The last eight files that failed, with the reason. Click a file name to edit the image. **Try failed files again** queues them again. See [Messages on files](#messages-on-files).

---

## Compression tab

Click **Save changes** at the bottom after changing anything. New settings apply to new uploads right away. To apply them to images that are already optimized, run a bulk optimization with **Also redo images optimized with different settings**, or click **Re-optimize** on single images.

### Compression level

| Level | Description in the plugin | JPEG | PNG | WebP copy | AVIF copy |
|---|---|---|---|---|---|
| **Lossless** | Every pixel stays the same, in the WebP copies too. Smaller files from better compression and removed metadata only. Only PNG and GIF images get (lossless) WebP copies; no AVIF copies are made. | Lossless only | Lossless | Lossless, PNG and GIF images only | None |
| **Balanced** (default) | Recommended. Big savings with no visible difference. | Quality 82 | Lossless | Quality 80 | Quality 62 |
| **Aggressive** | Smallest files. Fine for most photos; may soften fine detail. | Quality 72 | Colours reduced (lossy) | Quality 70 | Quality 50 |
| **Custom** | Choose the quality per format. | Slider 30–100 (default 82) | Your choice | Slider 30–100 (default 80) | Slider 20–100 (default 62) |

With **Custom**, sliders for **JPEG**, **WebP** and **AVIF** appear, plus **Reduce PNG colours (lossy, much smaller)**. That PNG option is greyed out when no engine on the server can compress PNG files.

Good to know:

- **Lossless** keeps every pixel the same in everything visitors get, copies included. PNG and GIF images get a lossless WebP copy when it's smaller (made with Imagick, or with GD on PHP 8.1 and newer). JPEG photos get no WebP copy, because a lossless WebP of a photo is practically always larger than the JPEG, and no image gets an AVIF copy. For WebP and AVIF copies of photos, choose **Balanced**.
- Resizing (see [Resize large uploads](#resize-large-uploads)) changes the pixels of oversized uploads at every level; turn it off if even those must stay untouched.
- A JPEG that is already compressed at (about) the chosen quality or lower is never compressed again with loss; it only gets lossless optimization.
- The plugin always starts from the kept original, so re-optimizing never compresses an already compressed image a second time.
- A file is only replaced when the result is meaningfully smaller (at least 1 KB or 1%). Otherwise it is marked **Already optimal**.

### What to do

| Setting | What it does | Default |
|---|---|---|
| **Optimize new uploads automatically** | "Runs in the background a few seconds after each upload." Also applies when WordPress regenerates or edits an image's files. | On |
| **Create WebP copies** | Saves a WebP copy next to each image when it's smaller than the optimized file. With **Lossless**, only PNG and GIF images get one (lossless). | On |
| **Create AVIF copies** | Saves an AVIF copy next to each image when it's smaller than both the optimized file and the WebP copy. Not made with **Lossless**. | On |
| **Remove metadata (EXIF, GPS location, camera details)** | Removes EXIF, XMP, IPTC and comments. "Colour profiles and photo orientation are kept." | On |
| **Optimize PDF files** | "With the engine for PDF compression on the Engines tab. Signed PDFs are never changed." Imagick and GD can't compress PDFs, so this is greyed out: "Not available: this server has no engine that can compress PDFs." | On (greyed out) |

**Create WebP copies**, **Create AVIF copies** and **Optimize PDF files** are greyed out when the server can't do them, with an explanation, for example "Not available: no engine on this server can write AVIF. Ask your host for PHP 8.1+ with AVIF support in GD, or Imagick with AVIF."

### Resize large uploads

| Setting | What it does | Default |
|---|---|---|
| **Scale down images larger than:** | Turns resizing on or off. Needs Imagick or GD. | On |
| **Max width** | 320 to 10000 px. | 2560 px |
| **Max height** | 0 for no limit, otherwise 320 to 10000 px. | 0 (no limit) |

"Only the main uploaded file is scaled; WordPress makes the smaller sizes from it." Resizing applies to JPEG, PNG and WebP files (not animated images). The image keeps its proportions. The plugin also makes sure WordPress's own "big image" scaling never shrinks uploads below your maximum.

### Originals

| Setting | What it does | Default |
|---|---|---|
| **Keep the originals so changes can be undone** | Stores each original in `uploads/symelo-images/backups` before changing it. "Without them, 'Restore original' only removes the WebP and AVIF copies." | On |
| **Also optimize the full-size original WordPress keeps of scaled photos** | When WordPress scales a large photo, it keeps the untouched upload as well (for example `photo.jpg` next to `photo-scaled.jpg`). "That file isn't shown on the site, but takes space." | Off |

When the full-size original isn't optimized, its metadata (such as GPS location) is still removed if **Remove metadata** is on, because the file is publicly reachable. The pixels stay the same. With **Keep the originals** on, the file is backed up first like every other file, so restoring the image brings its metadata back.

### Exclude image sizes

"Checked sizes are left exactly as WordPress made them: they aren't compressed and get no WebP or AVIF copies. Leave everything unchecked to optimize every size."

Every image size registered by WordPress, your theme and plugins is listed with its dimensions (∞ means no limit). The counter shows "Every size is optimized." or "Sizes excluded: *N*". Default: nothing excluded.

Excluding a size only affects images optimized afterwards, as the card says: "Excluding a size doesn't change images that are already optimized. To put that size back as WordPress made it, restore the images' originals, then optimize them again." **Re-optimize** alone doesn't do it, because it leaves excluded sizes alone.

---

## Delivery tab

Click **Save changes** after changing anything on this tab.

### Serving WebP and AVIF

| Option | How it works | When to use it |
|---|---|---|
| **&lt;picture&gt; tags (recommended)** (default) | "Works on every server and with every cache or CDN. Each image gets AVIF and WebP sources, and browsers pick the best one they support. Only images on this site's own address are covered: images loaded from another address (for example a CDN that rewrites image URLs) are left as they are." The plugin rewrites the page's HTML: each `<img>` that has copies is wrapped in a `<picture>` element with AVIF and WebP sources. A small CSS rule keeps the wrapper out of the layout, so images look and behave as before. | Almost always. |
| **Server rules** | "Keeps the HTML unchanged: the server sends the WebP/AVIF copy at the original URL." Rules are written to `wp-content/.htaccess` for Apache or LiteSpeed; Nginx needs a snippet in its configuration. "Not for sites behind Cloudflare or most other CDNs: they cache one version per URL for everyone." | When you can't or don't want to change the HTML, and you have no CDN in front of the site. |
| **Don't serve them** | "Only create the copies (for example when a CDN converts images itself)." | When something else serves modern formats. |

**&lt;picture&gt; tags in detail:**

- Works on the whole page, so theme and plugin images are covered, not only images in your posts.
- Only images whose address is on your own site (in uploads or wp-content) and that have a copy on disk are wrapped. Images loaded from another address, for example through a CDN or image service that rewrites image URLs to its own domain, are never wrapped: the plugin can't tell which file they belong to. **Test now** lists such images it finds on your home page.
- Only the formats ticked on the **Compression** tab are offered. Unticking **Create AVIF copies** stops offering AVIF right away.
- Images that use another lazy-loading script (`data-src` or `data-lazy-src` attributes) aren't wrapped.
- To keep an image out, give it the class `no-picture` (or `skip-picture`, `symelo-no-picture`), or the attribute `data-skip-picture`.
- Feeds, the REST API, AJAX requests and non-HTML responses are never changed.

**Server rules in detail:**

- When you switch to **Server rules** and save, the plugin writes the rules and tests them right away (see [Check delivery](#check-delivery)).
- The rules go between `# BEGIN Symelo Image Optimizations` and `# END Symelo Image Optimizations` in `wp-content/.htaccess`. They answer a request for `photo.jpg` with `photo.jpg.avif` or `photo.jpg.webp` when the browser accepts that format and the copy exists, and add a `Vary: Accept` header.
- The rules only serve the formats ticked on the **Compression** tab. When you tick or untick **Create WebP copies** or **Create AVIF copies** and save, the rules are rewritten right away, so unticking AVIF stops serving AVIF at once. On Nginx, copy the updated snippet (see [Nginx configuration](#nginx-configuration)).
- If the rules disappear (for example after a redeploy), the plugin puts them back the next time an administrator opens wp-admin, and in its daily maintenance.
- Switching to another option, or deactivating the plugin, removes the rules.
- If the file can't be written: "wp-content/.htaccess can't be written. Add the rules manually or ask your host."

If the mode is set in wp-config.php, the options are greyed out: "Set by SYMELO_IMAGES_DELIVERY in wp-config.php."

### Lazy loading

| Setting | What it does | Default |
|---|---|---|
| **Load images only when they are about to scroll into view** | "Uses the browser's built-in lazy loading: no scripts, no layout shifts." Turning it off also turns off WordPress's own lazy loading. | On |
| **Load the first … images right away** | 0 to 20. "Images at the top of the page (logo, hero) should load immediately so the page appears fast." | 3 |
| **Also lazy-load theme and plugin images and embeds** | "WordPress only handles images in the content." With this on, the plugin adds lazy loading to every image and iframe (videos, maps) on the page. | On |

Details of the whole-page lazy loading:

- The first images on the page load right away even if a theme or plugin marked them lazy, so your main image isn't delayed.
- Small icons (64×64 pixels or less, with their size set in the HTML) don't count towards "the first images".
- Images marked `fetchpriority="high"` are never lazy-loaded.
- To exclude an image or iframe, add the class `skip-lazy` (or `no-lazy`, `symelo-no-lazy`) or the attribute `data-skip-lazy`.

### Responsive images

| Setting | What it does | Default |
|---|---|---|
| **Add extra image sizes (breakpoints)** | "Fills the gaps between WordPress's sizes so phones, tablets and large screens each download an image close to the size they show." | Off |
| **Widths** | Comma-separated widths in pixels, 100 to 4000, at most 12. | 480, 1280, 1920 |
| **Tell browsers how wide images really are** | Sets the `sizes` attribute of image, gallery, cover, featured image, media & text and WooCommerce product grid blocks from your theme's layout, "so browsers don't download bigger files than needed." | On |

"New uploads get these sizes. 'Optimize all images', and 'Optimize now' or 'Re-optimize' in the media library, add them to existing images." WP-CLI's `optimize` command adds them too. The extra sizes are named `symelo-480`, `symelo-1280` and so on.

**Tell browsers how wide images really are** reads the content and wide widths from your block theme's settings (or the classic `$content_width`). It leaves WordPress's own value alone where it can't know the width, for example featured images inside query loops.

### Check delivery

Click **Test now**. What it checks depends on the delivery option:

- **&lt;picture&gt; tags**: "Checks which formats this server can create, then looks at the home page the way a visitor gets it: which images have WebP/AVIF sources, and which load from another address."
- **Server rules**: "Requests a test image the way a browser does and checks which format comes back." The card also shows whether the rules are in place: **Rules are in wp-content/.htaccess** or **Rules are not in wp-content/.htaccess**.

Only the formats ticked on the **Compression** tab are tested.

| Result | Meaning |
|---|---|
| "&lt;picture&gt; delivery is on: pages offer WEBP, AVIF versions of optimized images to browsers that support them." | The server can make the copies (&lt;picture&gt; mode). One or more of the following sentences say what the test found on the home page. |
| "On the home page, *N* images have WebP/AVIF sources." | All good. |
| "No image on the home page has WebP/AVIF sources yet. Its images may not be optimized yet, or a page cache may still serve an older version of the page." | Optimize the images (bulk optimization), then purge your page cache and CDN cache, and test again. |
| "*N* images on the home page load from another address (*cdn.example.com*), so they can't get WebP/AVIF sources." | Those images come from another domain, for example a CDN that rewrites image URLs, and are never wrapped. If the CDN can convert images itself, choose **Don't serve them**. |
| "The home page has no images to check." | Nothing to check on the home page; the copies are still served wherever images appear. |
| "The home page couldn't be checked (…)." | The site couldn't load its own home page (blocked requests, HTTP authentication on a staging site). Check a page with your browser's developer tools instead. |
| "No WebP or AVIF copy could be made, so there is nothing to serve yet." | The server can't make copies. See the **Engines** tab. |
| "WebP and AVIF copies are turned off on the Compression tab, so there is nothing to serve." | Tick **Create WebP copies** or **Create AVIF copies**. |
| "Delivery is off: copies are created but not served by the plugin." | **Don't serve them** is selected. |
| "The server sends WebP/AVIF to browsers that support them." | All good (server rules). |
| "The server sends the modern formats, but without a 'Vary: Accept' header. Enable mod_headers (Apache) so caches keep the versions apart." | Works, but caches could mix up formats. |
| "The server sends WebP/AVIF correctly, but the site is behind *Cloudflare*, which caches one version of each image for every visitor…" | A CDN or caching proxy was detected (Cloudflare, Amazon CloudFront, Sucuri, Fastly, BunnyCDN or another caching proxy). Use **&lt;picture&gt; tags**, or make sure the CDN caches images separately per "Accept" header. |
| "The server still sends the original image. The rewrite rules aren't active on this server…" | The rules don't work here (no Apache/LiteSpeed with mod_rewrite, or the Nginx snippet is missing). Use **&lt;picture&gt; tags**. |

The test uses a small image in `uploads/symelo-images/public/` and, with **&lt;picture&gt; tags**, your home page.

### Nginx configuration

On Nginx servers, when **Server rules** is selected, or when the Site Health check recently found that kept originals can be downloaded (see [Privacy and security](#privacy-and-security)), the card shows **Nginx configuration** with a **Copy** button. Nginx ignores the `.htaccess` files the plugin relies on, so this snippet does their work:

- It always blocks web access to the plugin's private folders: the kept originals (which still have their GPS location) and temporary files.
- With **Server rules**, it also serves the WebP/AVIF copies at the original image addresses, for the formats ticked on the **Compression** tab. Copy it again when you change those.

The plugin shows the exact snippet for your site. With **Server rules** and both formats ticked, it looks like this:

```nginx
# In the http { } block:
map $http_accept $symelo_images_avif { default ""; "~*image/avif" ".avif"; }
map $http_accept $symelo_images_webp { default ""; "~*image/webp" ".webp"; }

# In the server { } block:
# Kept originals (with their GPS data) and temporary files stay private.
location ^~ /wp-content/uploads/symelo-images/backups/ { deny all; }
location ^~ /wp-content/uploads/symelo-images/tmp/ { deny all; }

# WebP/AVIF copies at the original image URLs.
location ~* ^/wp\-content/.+\.(jpe?g|png|gif|webp)$ {
    types { image/avif avif; image/webp webp; image/jpeg jpg jpeg; image/png png; image/gif gif; }
    add_header Vary Accept;
    try_files $uri$symelo_images_avif $uri$symelo_images_webp $uri =404;
}
```

With **&lt;picture&gt; tags** (or **Don't serve them**), only the server block part with the two `deny` lines is shown. Your host may need to add the snippet for you. Reload Nginx afterwards, then click **Test now**.

---

## Other Folders tab

Optimizes images that aren't in the media library: your theme's images, plugin assets and other folders inside wp-content. "The originals are kept like media files, so everything can be undone."

### Settings

| Setting | What it does | Default |
|---|---|---|
| **Active theme (*name*)** | Includes your active theme's folder (and the parent theme's, for a child theme). | Off |
| **Other folders** | "One per line, relative to wp-content", for example `plugins/my-plugin/assets` or `uploads/gallery`. You can also paste paths that start with `wp-content/` or the full server path. Up to 30 folders. | Empty |
| **Check these folders automatically and optimize new or updated images** | "New images are picked up once a day. Every 10 minutes, a quick check also notices images that a theme or plugin update (or a redeploy) put back, and optimizes them again." The quick check looks at a sample of optimized folder images; if any was reset, the folders are scanned and optimized again. | On |

Click **Save changes**. A folder that doesn't exist is refused: '"…" isn't a folder inside wp-content.' The plugin's own backups folder can't be added.

### Folder images

This card appears once folders are selected. It shows "*N* files found, *N* optimized, *P*% smaller for visitors.", the time of the last scan, and a list of up to 50 files with their **Status** and **Size**.

- **Scan folders**: finds new, changed and removed files ("Found *N* files: *N* new, *N* changed.").
- **Optimize folder images**: scans, then queues every file that isn't optimized yet, failed or was restored.

What the scan picks up: JPEG, PNG, GIF and WebP files (and PDFs, when **Optimize PDF files** is on and an engine can compress them). It skips `.git`, `.svn`, `node_modules` and `.cache` folders, folders that are symbolic links, files that belong to the media library, and the plugin's own WebP/AVIF copies. One scan stops after 20,000 files or 25 seconds: "The scan stopped early (too many files); select smaller folders."

Folder images are served as WebP/AVIF in the same way as media images. There's no way to restore single folder images, or only the folder images: **Advanced → Restore all originals** (or `wp symelo-images restore --all`) puts back every original, for the folder images *and* the whole media library.

---

## Engines tab

### Found on this server

The first line describes the server, for example "Linux on x86_64, PHP 8.3.12."

The table lists every engine:

| Column | Meaning |
|---|---|
| **Engine** | The name, and where it comes from, for example **PHP extension**. |
| **Version** | The detected version. |
| **Status** | **Available**, **Outdated** (found, but too old to be used) or **Not found**, with a note explaining why. |
| **Can do** | The jobs the engine handles. |

| Engine | Jobs |
|---|---|
| Imagick (ImageMagick) | JPEG, PNG, WebP and AVIF (each only if your ImageMagick build supports it), resizing |
| GD | JPEG, PNG, WebP and AVIF (each only if your GD build supports it), resizing |

Notes about the image libraries (see [Image library warnings](#image-library-warnings)) appear under the table.

### Engine for each job

For each job, choose **Automatic** or a specific engine. "Automatic picks the best engine available. If your choice can't handle a particular file, the next suitable engine is used." The **Automatic** entry shows which engine it currently picks, for example "Automatic (Imagick (ImageMagick))". Engines the server can't use are listed but disabled with "— not available".

| Job | Automatic order |
|---|---|
| **JPEG compression** | Imagick, GD |
| **PNG compression** | Imagick, GD |
| **WebP copies** | Imagick, GD |
| **AVIF copies** | Imagick, GD |
| **PDF compression** | None: "This server has no engine that can compress PDFs." |
| **Resizing** | Imagick, GD |

Imagick is preferred because it keeps colour profiles and handles CMYK and 16-bit images. GD is used for files Imagick declines, or when Imagick isn't installed.

Imagick and GD can't compress a JPEG losslessly, so with the **Lossless** level JPEGs only get their metadata removed.

When a job has no engine, the line under it says so, for example "This server has no engine that can compress PDFs."

Click **Save changes**. If an engine you chose isn't available: "*engine* can't do that job on this server, so Automatic is used instead." If a chosen engine disappears later (for example because the software that provided it was removed), **Automatic** is used for that job without a message.

---

## Advanced tab

### Background processing

| Row | Meaning |
|---|---|
| **Waiting in the queue** | Files waiting to be optimized, restored or cleaned up. |
| **Last run** | When the last background run happened, and how many files it handled in how many seconds. |
| **Started by the site itself** | The last time the site started a run by sending a request to itself. |
| **Started by WP-Cron** | The last time WordPress's scheduler started a run. |
| **Last start error** | Shown when the site couldn't send that request to itself. |

"The site starts short background runs itself, with WP-Cron as a fallback. On servers that block both, run 'wp symelo-images run --paced' from a system cron job; like the background runs, it follows the background speed below."

- **Process the queue now** runs one batch right away. As the card says, it works "without the background speed's pauses and load checks", because you asked for it.
- **Empty the queue** removes everything waiting (and cancels a bulk run). Files already optimized stay optimized.

### Background speed

"How hard bulk optimization may use the server. New uploads and 'Optimize now' are always handled right away."

| Speed | Plugin description | Work per run | Pause between runs | Encoder priority | Encoder threads | Bulk work waits when the load per CPU core is at least |
|---|---|---|---|---|---|---|
| **Fast** | Long bursts, one after another. For dedicated servers, or a first run at a quiet time. | 25 s | None | Normal | Encoder's default | Never |
| **Normal (recommended)** (default) | Steady work in the background. Bulk work waits when the server is overloaded. | 15 s | None | Lowered | 2 | 2.0 |
| **Gentle, for small servers** | Short bursts of about 5 seconds with 30-second pauses, and bulk work waits whenever the server is busy. Large libraries take longer, but visitors won't notice. | 5 s | 30 s | Lowest CPU and disk priority | 1 | 0.7 |

- A run never lasts longer than your server's PHP time limit allows.
- Encoder priority and threads apply to engines that run separate encoder programs (engines added by other plugins can). Imagick and GD run inside PHP and always use a single thread.
- Where the server reports it, you'll see "Server load right now: *X* per CPU core (*N* cores)." and, if it happened in the last hour, "Bulk work last waited for a busy server at …".

If the speed is set in wp-config.php: "Set by SYMELO_IMAGES_WORKER_SPEED in wp-config.php."

### When the plugin is deleted

| Setting | What it does | Default |
|---|---|---|
| **Delete its settings, records and the WebP/AVIF copies** | When you delete the plugin, also remove its settings, its database table and every WebP/AVIF copy. "Optimized images stay as they are. Kept originals stay in uploads/symelo-images/backups unless you delete or restore them first." | Off |

Click **Save changes**.

### Undo everything

Shows "*N* originals are kept."

- **Restore all originals**: puts every original back and removes all WebP and AVIF copies, for media and folder images. Runs in the background ("Restoring *N* files in the background."). Any bulk run is cancelled first.
- **Delete all kept originals**: deletes the backups to free disk space. Your optimized images stay, but "changes can then no longer be undone".

Both ask for confirmation first. **Restore all originals** asks "Put every original file back, for media and folder images? The optimized versions and all WebP/AVIF copies will be removed." **Delete all kept originals** warns that this can't be undone.

---

## Support tab

- **Get help**: "Most problems come down to what the server supports (see Engines), file permissions, or background requests being blocked (see Advanced)." Links to **What this server supports**, **Background processing status** and **Check Site Health**, plus an **Email support** button (office@symelo.com).
- **System information**: a plain-text report with the plugin's settings, file counts, queue, engines and versions, the engine chosen for each job, and server details. "No passwords or keys." Click **Copy system information** and paste it into your support email.
- **About Symelo LTD**: a link to symelo.com.

---

## In the media library

### Optimization column and field

In the media library's list view, an **Optimization** column shows each image's state. The same information appears as an **Optimization** field in the attachment details (grid view pop-up and the edit screen).

| State | Meaning |
|---|---|
| **Optimizing…** | Queued or being processed. The cell updates by itself. |
| **Saved *P*% (*before* → *after*)** | Optimized. With WebP/AVIF delivery on, *after* is what visitors download. **AVIF** and **WebP** badges show which copies exist. **Details** lists every size of the image. |
| **Already optimal** | Nothing could be made meaningfully smaller. |
| The error | Optimization failed (see [Messages on files](#messages-on-files)). |
| The reason, or **Skipped** | The file was skipped, for example because it's too large. |
| **Original restored** | The original was put back. |
| **Not optimized** | Not processed yet. |

Buttons (for users who can edit the image):

| Button | When shown | What it does |
|---|---|---|
| **Optimize now** | Not optimized or restored | Optimizes the image right away. |
| **Re-optimize** | Optimized | Optimizes every size again from the original, with the current settings. |
| **Restore original** | Optimized, with a kept original | Puts the original back and removes the copies. Asks "Put the original files back? The optimized versions and WebP/AVIF copies will be removed." |
| **Remove WebP/AVIF** | Optimized, but no original kept | Removes the copies only, after asking. |
| **Try again** | Failed or skipped | Tries again. For skipped files (for example over the size limit) it redoes every size of the image. |

**Optimize now**, **Re-optimize** and **Try again** also create missing breakpoint sizes when [breakpoints](#responsive-images) are on.

### Bulk actions

In the list view, tick images and choose from **Bulk actions**:

- **Optimize images**: optimizes the selected images again, even if they were optimized or skipped before. It doesn't create missing breakpoint sizes.
- **Restore original images**: restores the selected images.

A notice confirms: "*N* files queued. They are processed in the background."

When you delete an image from the media library, the plugin also deletes its WebP/AVIF copies, its kept original and its records.

---

## How each file type is handled

| File type | What happens |
|---|---|
| **JPEG** | Recompressed at the level's quality (or losslessly), saved as progressive JPEG, metadata removed (orientation and colour profile kept). WebP and AVIF copies (none with **Lossless**). |
| **PNG** | Recompressed losslessly, or with fewer colours with **Aggressive** (or **Custom** with **Reduce PNG colours** ticked). Metadata removed. WebP copy (lossless or lossy, whichever is smaller) and AVIF copy; with **Lossless**, a lossless WebP copy only. |
| **GIF** | Not recompressed. Still GIFs get a WebP copy, and an AVIF copy if Imagick or GD can make one (not with **Lossless**). Animated GIFs keep their animation only with an engine that supports it; Imagick and GD don't, so animated GIFs get no copies. |
| **WebP** (uploaded as WebP) | Not recompressed unless it needs resizing. Metadata removed. Can get an AVIF copy (not with **Lossless**). |
| **AVIF** (uploaded as AVIF) | Skipped: "AVIF files are already compressed." |
| **PDF** | Only with an engine for **PDF compression**, which Imagick and GD aren't, so PDFs are left as they are. Signed PDFs are never changed. |

Other rules:

- Files are recognised by their content, not their name.
- Copies are saved next to the file: `photo.jpg` gets `photo.jpg.webp` and `photo.jpg.avif`. A copy is only kept when it's smaller than the optimized file (and an AVIF copy only when it's also smaller than the WebP copy).
- Images over 60 megapixels or files over 100 MB are skipped: "The file is larger than the limit for optimizing."
- WebP and AVIF copies of CMYK JPEGs (common from print workflows) can only be made with Imagick.
- The image's width, height and file size in WordPress are updated after resizing or restoring.

---

## How-to recipes

### Serve WebP/AVIF behind Cloudflare

CDNs like Cloudflare usually cache one version of each image address for everyone. With **Server rules**, a browser that can't show AVIF might get an AVIF file that the CDN cached for someone else. **&lt;picture&gt; tags** avoid this, because each format has its own address.

1. Go to **Media → Image Optimizations → Delivery**.
2. Choose **&lt;picture&gt; tags (recommended)** and click **Save changes**.
3. Click **Test now** under **Check delivery**.
4. Purge your CDN's cache (and any page cache), so visitors get the updated HTML.

If your CDN converts images to WebP/AVIF itself, choose **Don't serve them** instead, or turn off the copies on the **Compression** tab to save disk space.

### Use server rules on Apache or LiteSpeed

1. Go to **Delivery**, choose **Server rules** and click **Save changes**.
2. The plugin writes the rules to `wp-content/.htaccess` and tests them right away. Read the message at the top of the page.
3. If it says the server still sends the original image, your server doesn't apply the rules; switch back to **&lt;picture&gt; tags**.

On Nginx, copy the snippet from **Nginx configuration** into your server configuration (or ask your host), reload Nginx, and click **Test now**.

### Optimize theme images

1. Go to **Other Folders**.
2. Tick **Active theme (*name*)**. Add other folders if you like, for example `plugins/my-slider/images`.
3. Keep **Check these folders automatically and optimize new or updated images** ticked, so images are optimized again after theme updates.
4. Click **Save changes**, then **Optimize folder images**.

The list under **Folder images** shows each file's status.

### Use the plugin on a small server

1. Go to **Advanced → Background speed**, choose **Gentle, for small servers** and click **Save changes**.
2. Optionally, on **Compression**, untick **Create AVIF copies**. AVIF is the slowest format to create.
3. Start the bulk optimization on **Overview**. It runs in short bursts with pauses, at the lowest priority, and waits while the server is busy. New uploads are still optimized right away.
4. If **Advanced → Background processing** shows no recent runs, your host may block the site's requests to itself. Ask your host to set up a system cron job:

   ```sh
   wp symelo-images run --paced --max-seconds=240
   ```

   for example every five minutes. With `--paced`, the command follows the Gentle speed like the background runs: short bursts, pauses, and waiting while the server is busy. `--max-seconds` ends each run before the next cron job starts.

### Change the compression level and redo existing images

1. On **Compression**, choose the new level and click **Save changes**.
2. On **Overview**, tick **Also redo images optimized with different settings** and click **Optimize all images**.

Because the plugin always starts from the kept originals, a higher quality really does bring back detail, as long as **Keep the originals** was on.

### Keep an image out of lazy loading or &lt;picture&gt;

Add a CSS class to the image (in the block editor: **Advanced → Additional CSS class(es)**):

- `skip-lazy`: always load right away.
- `no-picture`: never wrap in `<picture>` (the original format is always used).

### Free disk space

Kept originals can take as much space as your original library.

1. Make sure you're happy with the results (look at a few pages and images).
2. Go to **Advanced → Undo everything** and click **Delete all kept originals**.

After this, changes can't be undone. To stop keeping originals for future uploads, untick **Keep the originals so changes can be undone** on the **Compression** tab.

### Exclude an image size

If one image size must stay exactly as WordPress made it (for example a size used for print downloads):

1. On **Compression → Exclude image sizes**, tick that size and click **Save changes**.
2. If images were already optimized, restore them and optimize them again.

---

## Troubleshooting

### Site Health checks

**Tools → Site Health** includes four checks. The first three have the badge "Performance", the last one "Security":

| Check | Good result | Problem and fix |
|---|---|---|
| Images can be converted to WebP and AVIF | "This server can create both modern formats." | "This server can't create AVIF images" (or WebP). Ask your host to enable the format in Imagick or GD (see [WebP or AVIF isn't available](#webp-or-avif-isnt-available)). |
| Image libraries are up to date | "No image library with known security problems was found." | "An image library may need a security update." See below. |
| Image optimization runs in the background | "Queued images are being processed." | "Image optimization is stuck": files are waiting and nothing has run for 30 minutes. See [Images stay "Optimizing…"](#images-stay-optimizing). |
| Kept originals are private | "Files in uploads/symelo-images/backups can't be downloaded from the web." | "Kept originals can be downloaded by anyone": the server ignores the `.htaccess` files that protect them (Nginx, for example). Add the rules from **Delivery → Nginx configuration** to the server configuration (see [Nginx configuration](#nginx-configuration)), or delete the kept originals. "Access to kept originals couldn't be checked" means the site couldn't request the test file from itself. |

The privacy check requests a small test file, `uploads/symelo-images/backups/access-check.txt`, from your site the way anyone could. Its result is remembered for a day when the folder is private, an hour when the check failed, and ten minutes when the files can be downloaded, so a fix shows soon.

**Site Health → Info → Symelo Image Optimizations** lists every engine with its version and jobs, and the engine used for each job.

### Images stay "Optimizing…"

The background work isn't starting.

1. Go to **Advanced → Background processing**. If **Last start error** is shown, your server couldn't send a request to itself.
2. Click **Process the queue now**. If files get processed, the optimizer works and only the automatic start is blocked.
3. Common causes: a firewall or security plugin blocking requests from the site to itself, HTTP authentication on a staging site, or WP-Cron disabled (`DISABLE_WP_CRON`).
4. Fix: allow the site's requests to itself, or run `wp symelo-images run --paced` from a system cron job.

Keeping the **Overview** tab open also moves bulk work along.

### WebP or AVIF isn't available

The option is greyed out on **Compression**, or the Overview says **No** for **WebP copies** or **AVIF copies**.

- Open **Engines**. The notes say why each engine can't do it, for example "This GD build can't write AVIF." or "The Imagick PHP extension isn't installed."
- Ask your host for PHP 8.1+ with AVIF support in GD, or Imagick built with WebP/AVIF. Then reload the **Engines** tab.

### Images aren't served as WebP/AVIF

- Click **Delivery → Test now** and read the message.
- **&lt;picture&gt; tags**: check that the images were optimized (media library column) and that copies exist (**AVIF**/**WebP** badges). Clear your page cache and CDN cache. Images loaded from another domain (for example through a CDN that rewrites image addresses) are never wrapped; **Test now** names such domains when it finds them on your home page.
- **Server rules**: see the test result table in [Check delivery](#check-delivery).
- Your browser's developer tools show which format was downloaded (Network tab, "Type" column).

### Messages on files

These appear in the media library, on **Overview → Recent problems** and on the **Other Folders** list.

| Message | What to do |
|---|---|
| "The file can't be changed: the web server has no write permission." | Ask your host to make the uploads folder (or the chosen folder) writable for the web server. |
| "The file no longer exists." | The file was deleted outside WordPress. Nothing to do. |
| "This file is outside the folders the plugin may change." | The file isn't in uploads or a selected folder (or a link points outside). |
| "Not a supported image or PDF." | The file's content isn't JPEG, PNG, GIF, WebP or PDF, whatever its name says. |
| "AVIF files are already compressed." | Expected; AVIF uploads are left alone. |
| "The file is larger than the limit for optimizing." | Over 60 megapixels or 100 MB. A developer can raise the limits (see filters). Then click **Try again** under the image in the media library (or use the bulk action **Optimize images**). Bulk runs don't retry skipped files. |
| "The backups folder could not be created, so the file was left untouched." / "The original could not be backed up, so the file was left untouched." | Check that `uploads/symelo-images` can be created and written, and that there is free disk space. |
| "The optimized file could not be written." / "The temporary folder could not be created." | Disk full or permissions. |
| "No engine on this server can create WEBP." (or AVIF) | See [WebP or AVIF isn't available](#webp-or-avif-isnt-available). |
| "No PDF engine is available on this server." | Imagick and GD can't compress PDFs. Turn off **Compression → Optimize PDF files** if you don't want PDFs listed. |
| "GD could not read the image." / "ImageMagick: …" | The engine couldn't handle this file. Choose another engine for that job on **Engines** and try again. |
| "Processing stopped: …" / "Processing stopped unexpectedly several times. The image may be too large for this server's memory or time limits." | PHP ran out of memory or time. Ask your host for a higher memory limit, or exclude very large images. |
| "The original wasn't kept, so only the WebP and AVIF copies were removed." | Shown after **Restore** when **Keep the originals** was off. |

**Try failed files again** (Overview) or **Try again** (media library) queues failed files again. **Try again** also retries skipped images.

### Image library warnings

On **Overview**, **Engines** and in Site Health you may see warnings like:

- "ImageMagick *version* is older than *version*, which fixes published security issues…" Your host may have patched it without changing the version; if not, ask them to update ImageMagick (WordPress itself uses it for every upload).

If the library comes from a Linux distribution that backports security fixes (such as Debian or Ubuntu packages), the plugin shows a calmer note instead, asking you to keep the server's packages up to date.

### Still stuck?

Go to the **Support** tab, click **Copy system information**, and email it with a description of the problem to office@symelo.com.

---

## Privacy and security

### What is stored, and where

| Data | Where |
|---|---|
| Settings | The `symelo_images_settings` option in the database. |
| One record per optimized file: path, sizes, status, engines used, a checksum, errors | A database table, `{prefix}symelo_images`, per site. |
| WebP and AVIF copies | Next to each image (`photo.jpg.webp`, `photo.jpg.avif`). |
| Kept originals | `uploads/symelo-images/backups/`, plus the small `access-check.txt` file the Site Health privacy check requests. |
| Temporary files | `uploads/symelo-images/tmp/`. Leftovers older than a day are deleted daily. |
| Delivery test image | `uploads/symelo-images/public/`. |

The plugin adds `.htaccess` files that block web access to the backups and temporary folders on Apache and LiteSpeed. **On Nginx these files have no effect.** Kept originals still contain their GPS location and camera metadata, so on Nginx these folders must be blocked in the server configuration:

```nginx
location ^~ /wp-content/uploads/symelo-images/backups/ { deny all; }
location ^~ /wp-content/uploads/symelo-images/tmp/ { deny all; }
```

**Delivery → Nginx configuration** shows these lines with the exact paths of your site (see [Nginx configuration](#nginx-configuration)); the `^~` makes them win over image rules such as the plugin's own. Site Health checks that the kept originals can't be downloaded and warns you if they can (see [Site Health checks](#site-health-checks)), whatever web server you use.

### What is sent where

- **Images never leave your server.** All compression and conversion happens locally.
- **Background work**: your site sends short requests to itself (to `wp-admin/admin-ajax.php`, authenticated with a random per-site secret) and uses WP-Cron. The delivery test requests a test image (and, with &lt;picture&gt; tags, the home page) from your own site, and the Site Health privacy check requests a test file from the backups folder.
- **No external services.** The plugin doesn't send data to any outside service.
- Nothing is sent to Symelo LTD. The Support tab's links to symelo.com and its email link only do something when you click them.

### Metadata removal

With **Remove metadata** on, EXIF (camera, settings, GPS location), XMP, IPTC and comments are removed from optimized images and their copies. The full-size original that WordPress keeps of scaled photos also loses its metadata, even when it isn't optimized. The kept originals in the backups folder are untouched, which is why they must not be downloadable (see above).

### Security notes

- The plugin works with PHP's image libraries only. It doesn't run or download programs.
- Files are only changed inside uploads, wp-content and the folders you select. Symbolic links can't lead outside them.
- ImageMagick always gets an explicit file format and resource limits.
- Every admin action checks permissions and WordPress's nonce protection. The background worker accepts nothing but its per-site secret, and can only process what's already queued.

---

## Undoing and uninstalling

| You want to… | Do this |
|---|---|
| Undo one image | Media library → **Restore original**. |
| Undo several images | Media library list view → tick them → **Restore original images**. |
| Undo folder images | **Advanced → Restore all originals**. This restores the media library too; folder images can't be restored on their own. |
| Undo everything | **Advanced → Restore all originals**, then wait until **Waiting in the queue** is 0. |
| Stop serving WebP/AVIF | **Delivery → Don't serve them**. |
| Stop optimizing new uploads | **Compression** → untick **Optimize new uploads automatically**. |

Restoring only works for files whose original was kept (**Keep the originals** on, and **Delete all kept originals** not used). Without an original, restoring removes the WebP and AVIF copies only. This includes WordPress's full-size originals of scaled photos: with **Keep the originals** on, they are backed up before their metadata is removed, so restoring brings it back.

Restoring asks first: "Put the original files back? The optimized versions and WebP/AVIF copies will be removed." You can optimize the images again at any time afterwards.

**Deactivating** the plugin:

- stops all background work and scheduled tasks;
- removes the server rules from `wp-content/.htaccess`;
- stops the &lt;picture&gt; tags and the plugin's lazy loading, responsive sizes and breakpoint sizes (WordPress's own lazy loading continues);
- leaves optimized images, copies, kept originals and settings in place, so reactivating continues where you left off.

**Deleting** the plugin (Plugins → Delete):

- Always: removes scheduled tasks, the server rules, temporary files, the delivery test image and the background-worker state.
- If **Advanced → Delete its settings, records and the WebP/AVIF copies** is on: also deletes every WebP/AVIF copy, the database table and the settings.
- Never: your optimized images stay as they are, and kept originals stay in `uploads/symelo-images/backups`.

To return completely to your original images:

1. **Advanced → Restore all originals**.
2. Wait until **Advanced → Background processing → Waiting in the queue** shows 0.
3. Tick **Delete its settings, records and the WebP/AVIF copies** and click **Save changes**.
4. Deactivate and delete the plugin.
5. Delete the `uploads/symelo-images` folder if anything is left in it.

---

## For developers

### wp-config.php constants

Constants override the settings screen and lock the matching options.

| Constant | Values | Setting |
|---|---|---|
| `SYMELO_IMAGES_DELIVERY` | `picture`, `rewrite` or `none` | **Delivery → Serving WebP and AVIF** |
| `SYMELO_IMAGES_WORKER_SPEED` | `fast`, `normal` or `gentle` | **Advanced → Background speed** |

Invalid values for `SYMELO_IMAGES_DELIVERY` and `SYMELO_IMAGES_WORKER_SPEED` fall back to the defaults (`picture`, `normal`).

### Filters

| Filter | Default | Purpose |
|---|---|---|
| `symelo_images_capability` | `'manage_options'` | Capability for the settings screen, bulk optimization and the REST API. Optimizing and restoring single images in the media library (buttons, bulk actions) only needs the right to edit that image (`edit_post`), so authors can optimize their own uploads. |
| `symelo_images_engine_order` | Engines sorted by rank | `( array $engine_ids, string $task )`. The automatic order per job (`jpeg`, `png`, `webp`, `avif`, `pdf`, `resize`). |
| `symelo_images_max_pixels` | `60000000` | Images with more pixels are skipped. |
| `symelo_images_max_bytes` | `100 * MB_IN_BYTES` | Larger files are skipped. |
| `symelo_images_imagick_limits` | memory 256 MB, map 512 MB, disk 2048 MB, 1 thread, 180 s | `Imagick::RESOURCETYPE_*` constant name => limit. |
| `symelo_images_pace` | See [Background speed](#background-speed) | `( array $profile, string $speed )`. Keys: `budget`, `pause`, `nice`, `idle_io`, `threads`, `max_load`. |
| `symelo_images_worker_budget` | The speed's budget | Seconds a background run works before handing over (minimum 5). |
| `symelo_images_dispatch_args` | Non-blocking POST | `wp_remote_post()` arguments for the request the site sends to itself. |
| `symelo_images_skip_page` | `false` | Return `true` to skip the whole-page pass (&lt;picture&gt; and lazy loading) for the current request. |
| `symelo_images_more_from_symelo` | `[]` | Products listed under "More from Symelo LTD" on the Support tab. |
| `symelo_images_tabs` | `[]` | Extra tabs on the plugin screen (`slug => label`); see `docs/extending.md`. |
| `symelo_images_warnings`, `symelo_images_notes` | The library checks | `( string[] $items, Plugin $plugin )`. Warnings (Overview) and notes (Engines) also shown in the system information and `GET /status`. |
| `symelo_images_system_info` | The report's sections | `( array $sections, Plugin $plugin )`. Sections of the Support tab's system information. |
| `symelo_images_rest_status` | The status report | `( array $status, Plugin $plugin )`. The `GET /status` response. |

### Actions

| Action | Arguments | When |
|---|---|---|
| `symelo_images_loaded` | `Plugin $plugin` | The plugin has loaded. |
| `symelo_images_register_engines` | `EngineManager $engines` | The engine list is being built; add engines with `$engines->add()`. |
| `symelo_images_file_optimized` | `FileRecord $after, FileRecord $before` | After a file was optimized, failed, was skipped or was restored. |
| `symelo_images_engines_tab` | `Plugin $plugin` | At the end of the Engines tab, for add-on cards. |
| `symelo_images_tab_{$slug}` | `Plugin $plugin` | Renders the panel of a tab added with `symelo_images_tabs`. |

Add-ons build on these hooks and on the classes listed in `docs/extending.md`, which also explains engines, ranks and the `SYMELO_IMAGES_API_VERSION` constant (currently `1.0`).

### HTML classes and attributes

| Class or attribute | Effect |
|---|---|
| `skip-lazy`, `no-lazy`, `symelo-no-lazy`, `data-skip-lazy` | Never lazy-load this image or iframe. |
| `no-picture`, `skip-picture`, `symelo-no-picture`, `data-skip-picture` | Never wrap this image in `<picture>`. |
| `fetchpriority="high"` | Never lazy-loaded. |

### WP-CLI

| Command | What it does |
|---|---|
| `wp symelo-images status` | Engine per job, file totals, savings and queue size, plus library warnings. |
| `wp symelo-images optimize <id>... [--force]` | Optimizes these attachments now (and creates missing breakpoint sizes). `--force` redoes files that are already optimized. |
| `wp symelo-images optimize --all [--force]` | Every image in the media library (and PDF, when PDFs are optimized). |
| `wp symelo-images restore <id>...` | Restores these attachments. |
| `wp symelo-images restore --all [--yes]` | Restores every optimized file, including folder files, and removes all copies. |
| `wp symelo-images run [--max-seconds=<seconds>] [--paced]` | Processes the queue until it is empty (or the time is up, minimum 10 seconds). Use this from a system cron job. Without `--paced` it works without pauses; with `--paced` it follows **Background speed** like the background runs (slice length, pauses between bulk slices, waiting while the server is busy). |
| `wp symelo-images folders` | Scans the selected folders and optimizes new or changed files. |

### REST API

All routes are under `/wp-json/symelo-images/v1/` and require the plugin capability (an administrator, for example with an application password). Attachment routes also check that you may edit each attachment.

| Method and route | Parameters | What it does |
|---|---|---|
| `GET /status` | – | Full report: version and API version, platform, engines, jobs, warnings and notes, settings, totals, bulk progress, worker state, delivery (including the privacy check of the backups folder, `delivery.private_files`). |
| `GET /settings` | – | All settings. |
| `POST /settings` | JSON body with any setting keys | Changes only the keys you send, validated like the settings screen. Returns the settings and any errors. |
| `POST /optimize` | `ids` (array, up to 500), `force` (bool), `now` (bool) | Queues attachments; with `now`, processes them in the request. |
| `POST /restore` | `ids`, `now` | Restores attachments. |
| `GET /attachments/{id}` | – | The records of one attachment. |
| `GET /bulk` | – | Bulk progress. |
| `POST /bulk` | `action` (`start`, `pause`, `resume`, `cancel`), `reoptimize`, `media` (default true), `folders` | Controls the bulk run. |
| `POST /worker` | `seconds` (5–60, default 20) | Processes the queue for a while in this request (an alternative to WP-CLI for external cron). |
| `POST /delivery/test` | – | Runs the delivery test. With &lt;picture&gt; tags, `page` has the home page check (`images`, `pictures`, `foreign`, `hosts`) and `ok`/`message` describe it. |
| `POST /folders` | `action` (`scan`, `optimize`) | Scans or optimizes the selected folders. |
| `POST /maintenance` | `action` (`restore_all`, `drop_backups`, `clear_queue`, `retry_failed`) | The maintenance actions from the Advanced and Overview tabs. |

### Scheduled tasks

| Hook | Schedule | Purpose |
|---|---|---|
| `symelo_images_daily` | Daily | Deletes stale temporary files, restores missing server rules, scans selected folders. |
| `symelo_images_worker` | Every minute, only while work is queued | Fallback that starts background runs. |
| `symelo_images_worker_next` | One-off | Starts the next run after a pause (Gentle speed or a busy server). |
| `symelo_images_folder_check` | Every 10 minutes, while folders are selected and the daily check is on | Re-optimizes folder images that were reset. |

All of them are removed on deactivation.
