When SVGO's Defaults Aren't Enough: Writing a Custom Plugin to Strip Editor Metadata
SVGO's default preset won't strip editor-specific junk like Inkscape's namespaced attributes. A small custom plugin — using the v3 visitor API — turns SVGO into an SVG hygiene enforcer, not just a minifier.
12 Aug 2025, 18:42 UTC

Your design team exports icons from Inkscape or Illustrator, and every file drags along a payload of editor-specific markup: sodipodi:namedview elements, inkscape:label attributes, sometimes entire <metadata> blocks containing RDF license stubs. SVGO's default preset shrinks the geometry nicely, but a surprising amount of that editor residue survives — because the defaults are conservative about anything that looks like it might carry meaning.
The useful takeaway: SVGO isn't just a minifier, it's a plugin pipeline, and writing a small custom plugin to strip your own known-junk artifacts is a supported, stable practice. It turns SVGO from "make files smaller" into "enforce SVG hygiene in the build."
The default preset is a floor, not a policy
Running svgo with no config applies preset-default, a curated bundle of roughly thirty plugins: comment removal, group collapsing, path data conversion, and so on. It's deliberately safe. It removes generic cruft but keeps namespaced editor attributes in many cases, because removing attributes it doesn't understand could break someone's workflow.
You can tune the preset without abandoning it. Disabling a single plugin inside the preset looks like this in svgo.config.mjs:
export default {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false, // keep responsive scaling intact
},
},
},
],
};That override pattern matters: removeViewBox is disabled by default in recent versions precisely because stripping viewBox breaks SVGs that scale responsively without explicit width and height. If you ever see advice to enable it for extra bytes, don't — unless every asset has fixed dimensions.
A custom plugin for Inkscape residue
SVGO v3 (released in 2023) changed the plugin API. Custom plugins are now plain objects with a name and a fn that returns a visitor — the old string-typed plugin styles are gone. The visitor pattern lets you hook element entry and walk the AST. Here's a minimal plugin that removes Inkscape's sodipodi:namedview element and any attribute in the inkscape: or sodipodi: namespaces:
// svgo.config.mjs — requires SVGO v3, ESM-only
const stripEditorArtifacts = {
name: 'stripEditorArtifacts',
fn: () => ({
element: {
enter: (node, parentNode) => {
// Remove the whole sodipodi:namedview element
if (node.name === 'sodipodi:namedview') {
parentNode.children = parentNode.children.filter(
(child) => child !== node
);
return;
}
// Strip editor-namespaced attributes from every element
for (const attr of Object.keys(node.attributes)) {
if (attr.startsWith('inkscape:') || attr.startsWith('sodipodi:')) {
delete node.attributes[attr];
}
}
},
},
}),
};
export default {
plugins: [
{
name: 'preset-default',
params: { overrides: { removeViewBox: false } },
},
stripEditorArtifacts,
],
};Run it from your project root with npx svgo -f icons/ -o icons-dist/ (no special permissions needed; -f processes a folder). Then diff one output file against its source — you should see the sodipodi:namedview block and the namespaced attributes gone, with geometry untouched. Don't trust a byte-size check alone; open the before and after side by side in a browser at least once per asset class.
If you prefer scripting, the Node API is equally direct: import { optimize } from 'svgo', then const { data } = optimize(svgString, config). Errors surface as thrown exceptions, not result codes, so wrap calls in try/catch inside a build step.
Version pinning is part of the deal
Two practical cautions before you commit this to CI. First, SVGO v3 is ESM-only — it dropped CommonJS — so a CommonJS project either needs a dynamic import(), an .mjs config, or a pinned v2 dependency. Check what you have with npx svgo --version before writing config.
Second, the default preset's contents and default precision settings shift between major versions. A routine npm update can silently change the output of every committed SVG in your repo, producing noisy diffs or — worse — subtle rendering changes from more aggressive numeric rounding. Pin the exact version in package.json, and treat major upgrades as a deliberate task: read the migration notes shipped with the installed package, re-run your whole icon set, and eyeball the diff.
The trade-off: lossy by design, sometimes
Some plugins change rendering, not just bytes. convertPathData rounds coordinates to a configurable precision — fine for icons, risky for detailed illustrations where rounding shows up as kinked curves. collapseGroups can alter how inherited styles apply. The answer isn't to fear the preset but to tune floatPrecision per asset type and to keep a visual check in the loop. A handful of reference renders (even just screenshots committed alongside the assets) catches what no unit test on markup strings will.
The actionable close: pick one real SVG from your project, run it through the config above, and diff the result. If the output is smaller and renders identically, you've just turned SVGO into an enforceable hygiene rule — and the next editor-metadata blob that tries to sneak into the repo gets stripped automatically instead of reviewed by hand.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.