Configuring SVGO to Preserve Critical IDs While Optimizing Everything Else
Learn how to configure SVGO's cleanupIDs plugin to preserve specific SVG IDs required for animations and CSS targeting while still applying all other default optimizations. Includes a complete svgo.config.js example, CLI verification steps, and a test SVG you can run immediately.
28 Apr 2026, 04:24 UTC

The Problem: Optimization Breaks Animation and Styling Hooks
SVGO's default preset strips id attributes from SVG elements because they're often unused bloat. But in production workflows, those IDs are frequently the anchors for CSS animations, JavaScript targeting, or <use> references. Removing them saves bytes but breaks the frontend. The engineering decision isn't whether to optimize—it's how to optimize selectively without manual post-processing.
Desired Outcome
Run SVGO as part of a build pipeline (or ad-hoc via CLI) so that:
- All safe optimizations run: whitespace removal, metadata stripping, path minification, duplicate attribute elimination, etc.
- Specific
idvalues you declare as critical remain untouched. - No custom plugin code is required; configuration stays in
svgo.config.jsor CLI flags.
Prerequisites
- Node.js 16+ and npm/yarn/pnpm available.
- SVGO v2.x or v3.x installed locally in the project (
npm i -D svgo). Global installs hide version drift. - A sample SVG that contains at least one ID you must keep (e.g.,
id="logo-mark") and several you don't care about. - Ability to run a terminal command in the project root.
Step 1: Verify Version and Default Behavior
Run the following in the project root to confirm the installed version and see the default plugin list:
npx svgo --version
npx svgo --help | head -40
Expected output shows 2.x or 3.x. The help text lists --config, --disable, and --enable flags. If you see 1.x, stop—configuration syntax differs and this guide assumes v2+ multi-pass architecture.
Step 2: Create a Baseline SVG for Testing
Save the following as test-input.svg in a temporary folder. It contains three IDs: one critical (logo-mark), one referenced by a <use> (icon-star), and one purely decorative (deco-path-123).
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<title>Test SVG</title>
<desc>Generated for SVGO config verification</desc>
<g id="deco-path-123">
<path d="M10 10L90 90M90 10L10 90" stroke="#000" stroke-width="2" fill="none"/>
</g>
<symbol id="icon-star" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<use href="#icon-star" x="20" y="20" width="30" height="30" id="logo-mark"/>
</svg>
Step 3: Run Default Optimization to See the Damage
npx svgo test-input.svg -o test-default.svg
cat test-default.svg
Observe that id="deco-path-123", id="icon-star", and id="logo-mark" are all gone. The <use> now points to a non-existent fragment, and any CSS targeting #logo-mark fails silently.
Step 4: Configure Selective ID Preservation
Create svgo.config.js in the project root with the following content. This keeps the default preset but overrides cleanupIDs to preserve only the IDs you list.
/** @type {import('svgo').Config} */
module.exports = {
multipass: true,
js2svg: { pretty: true, indent: 2 },
plugins: [
{
name: 'preset-default',
params: {
overrides: {
cleanupIDs: {
keepIDs: ['logo-mark', 'icon-star'],
// minify: true, // default true; shortens other IDs
// prefix: '', // default ''; no prefix added
},
},
},
},
],
};
Key points:
multipass: true(default in v2+) lets plugins run repeatedly until the output stabilizes—important becausecleanupIDsmay create new minification opportunities.overridestargets a single plugin inside the preset without rewriting the entire plugin list.keepIDsaccepts an array of exact string matches. It does not support globs or regex.
Step 5: Run with the Custom Config
npx svgo --config svgo.config.js test-input.svg -o test-configured.svg
cat test-configured.svg
Verify the output:
id="logo-mark"andid="icon-star"are present and unchanged.id="deco-path-123"is removed (or minified to a short hash ifminify: true).- Whitespace, comments,
<title>,<desc>, and redundant attributes are still stripped.
Step 6: Automate in a Build Script (Optional)
Add a script to package.json so the whole team uses the same config:
"scripts": {
"optimize:svg": "svgo --config svgo.config.js 'src/assets/**/*.svg' -o 'dist/assets/'"
}
Run with npm run optimize:svg. The glob passes every SVG through the same pipeline; each file keeps only its own declared critical IDs.
Expected Checks
- File size delta: Compare
test-default.svgvstest-configured.svg. Expect a 10–30% larger output when preserving IDs, per SVGO documentation. If the increase exceeds 50%, audit whetherkeepIDslist is too broad. - Reference integrity: Open
test-configured.svgin a browser. The<use>element should still render the star. Inspect the DOM—#logo-markand#icon-starexist. - No regression in other plugins: Spot-check that
removeComments,collapseGroups,convertPathData, etc., still applied. The pretty-printed output makes this readable.
Recovery Options
If the wrong IDs are kept
Edit keepIDs array and re-run. No state is mutated outside the output files.
If a plugin conflict appears (rare)
Some plugin combinations assume execution order. If output looks malformed, disable the suspect plugin explicitly:
plugins: [
{ name: 'preset-default', params: { overrides: { cleanupIDs: { keepIDs: [...] } } } },
{ name: 'removeUnknownsAndDefaults', active: false }, // example
]
Then file an issue upstream; custom plugin order is fragile.
Rollback to default
Delete svgo.config.js or run npx svgo input.svg -o output.svg without --config. The original behavior returns instantly.
Limitations and Gotchas
- Exact match only:
keepIDs: ['icon-']does not keepicon-star. List every full ID. - Cross-file references: If
fileA.svguses<use href="fileB.svg#icon">, both files must preserveicon. SVGO processes files independently; coordinate thekeepIDslist across configs or use a shared config module. - Version drift: v3.x may rename or restructure plugins. Pin SVGO in
package.json("svgo": "^3.0.0") and test after upgrades. - Minification side-effect: With
minify: true(default), non-kept IDs become short hashes like_a. If your CSS/JS accidentally depends on those generated names, setminify: falseor add them tokeepIDs.
Quick Verification Checklist
| Check | Command / Action | Pass Criteria |
|---|---|---|
| Version | npx svgo --version | ≥ 2.0.0 |
| Config loads | npx svgo --config svgo.config.js --help | No error, shows custom config path |
| IDs preserved | grep -c 'id="logo-mark"' test-configured.svg | Returns 1 |
| Unwanted IDs gone | grep 'deco-path-123' test-configured.svg | No match |
| Use reference works | Open in browser | Star renders at (20,20) |
When to Revisit This Config
- New animation or tracking IDs are added to SVGs—update
keepIDs. - SVGO major version bump—read changelog for
cleanupIDschanges. - Bundle size budget tightens—audit
keepIDslist for stale entries.
This approach keeps the optimization pipeline standard and auditable while giving you surgical control over the one feature that most often breaks production UIs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.