Answer
SVGO silently ignores any plugin name it does not recognize, so a plugin that was renamed or removed between major versions produces no error — optimization simply proceeds without that plugin's transformations. There is no built-in config-validation API, so you must compare your config's plugin names against the installed version's built-in plugin list yourself, and keep your own backup of the source SVG for rollback.
Confirmed facts vs. assumptions
- Confirmed: unknown plugin names are skipped without warning in both SVGO v2 and v3; the
info object returned by optimize() carries no plugin warnings.
- Confirmed: in SVGO v3+, the built-in plugin list is exposed as
builtinPlugins from the svgo package, so you can diff your config against it programmatically.
- Assumption: you are on SVGO v3 or later (the v2-to-v3 rename/merge is the usual trigger). Run the detection script against the exact version deployed in production — the plugin list also changes across minor releases.
Detecting renamed or removed plugins
// check-svgo-plugins.mjs — run with the same svgo version used in production
import { builtinPlugins } from 'svgo';
import { loadConfig } from 'svgo';
const known = new Set(builtinPlugins.map(p => p.name));
const config = await loadConfig('./svgo.config.mjs');
const names = (config.plugins ?? []).map(p =>
typeof p === 'string' ? p : p.name
);
const unknown = names.filter(n => !known.has(n));
if (unknown.length) {
console.error('Unknown or renamed plugins:', unknown);
process.exit(1); // abort instead of silently optimizing
}
When a name is missing, either map it to its current equivalent via a version-specific alias table you maintain, or fail the build. Failing closed is the safer default when the plugin touches viewBox, IDs, or other attributes downstream consumers depend on.
Rollback strategy
- Before calling
optimize(), retain the original SVG string (or copy the file). SVGO is a one-way transform; nothing is stored for you.
- After optimization, verify the critical invariants — e.g., the root
viewBox is still present and unchanged, and required element IDs still exist.
- On mismatch, restore the backup and surface an error rather than shipping the altered output.
import { optimize } from 'svgo';
import fs from 'node:fs';
const source = fs.readFileSync('icon.svg', 'utf8');
const backup = source; // or fs.copyFileSync for on-disk rollback
const { data } = optimize(source, config);
const mustKeep = /viewBox="[^"]+"/.exec(source)?.[0];
if (mustKeep && !data.includes(mustKeep)) {
fs.writeFileSync('icon.svg', backup); // restore
throw new Error('Optimization altered viewBox; rolled back.');
}
Note: a plain file copy may not preserve permissions or timestamps; use fs.copyFileSync with COPYFILE_EXCL or an explicit metadata copy if downstream tools depend on them.
Verification
- Run the detection script with a deliberately stale plugin name and confirm it exits non-zero.
- Optimize a test SVG whose config contains only an unknown plugin name and confirm the output differs from a run with the corrected name — proving the silent-skip behavior.
- Confirm the rollback path restores a byte-for-byte identical source file.
One open detail
Which attributes must remain invariant for your consumers (viewBox, specific IDs, classes)? That determines exactly what the post-optimization check should compare.