Architecture note: SVGO’s removeViewBox plugin and plugin pipeline design
Architecture note describing the requirements, design, trust boundaries, operational checks, failure modes, and evolution conditions for SVGO’s removeViewBox plugin.
20 May 2026, 03:39 UTC

Problem
SVG files often carry a viewBox attribute that duplicates size information already present in explicit width and height attributes. Removing this redundant attribute can reduce file size without altering the rendered graphic, but the removal must be safe: the visual output must stay identical.
Requirements
- Detect when an SVG has explicit
widthandheightvalues that allow theviewBoxto be inferred without changing rendering. - Remove the
viewBoxattribute only when the inferred value matches the existing one. - Integrate the check into SVGO’s existing plugin pipeline without breaking other optimizations.
- Make clear what runtime access plugins have, so users can assess trust implications.
- Provide a way for users to enable, disable, or reorder the plugin via configuration or CLI flags.
Smallest suitable design
SVGO processes an SVG through a pipeline of plugins. Each plugin receives the current AST node and may return a modified node (or the same node) to continue the pipeline. The removeViewBox plugin follows the per‑item plugin contract used in SVGO 2.x:
{
name: 'removeViewBox',
fn: function(item, params, info) {
// item is an SVGJS AST node representing the element
if (!item.isElem('svg')) return item;
const widthAttr = item.attr('width');
const heightAttr = item.attr('height');
const viewBoxAttr = item.attr('viewBox');
if (!widthAttr || !heightAttr || !viewBoxAttr) return item;
// Parse numeric values; ignore units other than px or unitless
const w = parseFloat(widthAttr.value);
const h = parseFloat(heightAttr.value);
const [minX, minY, vbWidth, vbHeight] = viewBoxAttr.value.split(' ').map(Number);
// Default preserveAspectRatio is 'xMidYMid meet'; removal is safe only when
// the viewBox origin is 0 0 and the dimensions match width/height.
if (minX === 0 && minY === 0 && vbWidth === w && vbHeight === h) {
item.removeAttr('viewBox');
}
return item;
}
}
The plugin needs only the AST node and the params object; it does not require external resources. In the default pipeline it runs after structural clean‑ups (e.g., removal of useless groups) and before ID‑related optimizations, ensuring the svg element is stable when the decision is made.
Trust/data boundaries
SVGO does **not** sandbox plugins. Each plugin is a regular JavaScript function executed in the host Node.js process, granting it full access to:
- the file system (
fsmodule) - environment variables and
process - network APIs
Consequently, a plugin can read or write arbitrary files, spawn subprocesses, or make network requests. Trust boundaries are therefore at the user level: only run plugins from trusted sources or inspect plugin code before use.
Operational checks
To verify that removeViewBox behaves as expected, run SVGO with explicit control over the plugin set:
# Enable only removeViewBox, disable other default optimizations
svgo --enable=removeViewBox --disable=cleanupIDs,removeTitle input.svg -o output.svg
Inspect output.svg: the viewBox attribute should be absent when the original file had matching width/height and a viewBox of the form "0 0 W H". If the attribute remains, either the width/height were not set, used non‑pixel units, or the viewBox origin differed.
You can also test error handling by adding a faulty plugin via a config file:
# myconfig.json
{
"plugins": [
{ "name": "removeViewBox" },
{ "name": "./bad-plugin.js" }
]
}
where bad-plugin.js exports a function that throws:
module.exports = function(item, params, info) {
throw new Error('test error');
};
Running svgo --config myconfig.json input.svg -o output.svg will cause SVGO to exit with a non‑zero status and print the error; no output file is produced. This shows that plugin errors are **not** caught and suppressed.
Failure modes
- Plugin throws an exception: SVGO propagates the exception, stops processing, and returns a non‑zero exit code.
- Plugin returns a malformed AST: SVGO attempts to continue; if the AST cannot be serialized, a later error occurs.
- Width/height use non‑pixel units (%, em, etc.): the plugin cannot safely compare to the unit‑less
viewBoxvalues and preserves the attribute. - ViewBox origin not 0 0: the plugin preserves
viewBoxto avoid altering rendering. - Mismatched aspect ratio: the attribute is retained.
Conditions that would change the design
- If SVGO introduced a capability‑based plugin system that limited file‑system or network access, the trust boundary discussion would need revision.
- Should the SVG spec add a new intrinsic size attribute (e.g.,
size), the plugin’s logic would have to compare against that attribute instead ofwidth/height. - If SVGO changed the plugin interface to a visitor‑based model where plugins return
{ element: { enter, exit } }objects, theremoveViewBoximplementation would need to be rewritten to match the new contract while preserving the same safety checks.
Limitations and practical verification
The plugin only removes viewBox when:
- Both
widthandheightare present and are either unit‑less numbers or numbers followed bypx. - The
viewBoxattribute starts with "0 0" (origin at the top‑left corner). - The numeric width and height equal the
viewBoxwidth and height.
If your SVGs use percentages, em, or a non‑zero min‑x/min‑y in viewBox, the attribute will be retained. To confirm the behavior for your file set:
svgo --enable=removeViewBox --disable=cleanupIDs input.svg -o output.svg
# Check for remaining viewBox attributes
if grep -o 'viewBox="[^"]*"' output.svg | grep -q .; then
echo 'viewBox still present'
else
echo 'viewBox removed'
fi
Comparing the file sizes of input.svg and output.svg gives a quick measure of the optimization impact.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.