Free SVG optimizer
SVG optimizer that shows its working
Free, and no account is involved: drop an SVG and ten passes rewrite it in this tab — editor metadata out, transform lists multiplied down to one matrix, coordinates rounded, dead defs and unused ids dropped. Every pass has its own switch and its own byte figure: on the sample export loaded by the button, the editor’s leftovers are worth 562 bytes and rounding every coordinate in the file is worth 78. The result is then drawn next to the original and the two are compared pixel by pixel, because a file 76% smaller that renders differently is not an optimization.
- 100% free
- No signup
- 10 passes
- Up to 2 MB
- Pixel-checked
Drop one .svg here, choose it below, or paste the markup. Every pass runs on a DOM built by the browser’s own XML parser in this tab.
Press Ctrl+V anywhere on the page to drop in SVG markup copied out of an editor — no need to click the field first.
Nothing loaded yet. An SVG straight out of a drawing program is typically 40-60% editor bookkeeping: a generator comment, an rdf metadata block, a sodipodi:namedview, layer names, and coordinates carrying nine decimal places that describe motion smaller than a wavelength of light. The ten passes below each remove one class of that, and each is costed in bytes on its own so you can see which one actually earned its place.
How to optimize an SVG
Load it, choose what may be removed, then check the two renders agree.
Drop the .svg in, or paste the markup
Files up to 2 MB, one at a time, or paste anything beginning with an XML declaration, a comment or an svg tag. The markup goes through the browser's own DOMParser in image/svg+xml mode, which means this page refuses exactly the files a browser refuses: a duplicate attribute, an unclosed tag or a stray ampersand comes back as the parser's own line-and-column message instead of a silent half-optimization. A .svgz is gzip already and has to be expanded before it can be read.
Switch off the passes you do not trust, and set the decimals
Nine of the ten are on to begin with; removing title and desc is the one that starts off, because on a graphic carrying role="img" the title element is the accessible name. The slider sets how many decimal places survive in coordinates — 2 by default, with transform matrices given three more, since a scale factor of 0.00375 rounds to nothing at two. Each row shows what that pass alone removed, in bytes, measured by running the pipeline again with it appended.
Compare the two renders before you take the file
The original and the result are drawn side by side at the same size, and both are rasterized into a 256 px box and compared channel by channel with alpha premultiplied. Zero differing pixels means the rounding cost you nothing visible; a few hundred means an edge moved and the slider wants a digit back. Then Copy puts the markup on the clipboard and Download writes it as name.min.svg.
Technical specifications
| Passes | 10, each switchable: comments, editor metadata, title and desc, style to attributes, default values, transform lists, numeric precision, unused ids and dead defs, attribute-free groups, whitespace between tags |
|---|---|
| Deliberately absent | convertPathData's geometry work, mergePaths, reusePaths, moveElemsAttrsToGroup, inlineStyles, removeHiddenElems, removeOffCanvasPaths — the passes that need real path arithmetic rather than tree surgery |
| Parser | DOMParser in image/svg+xml mode and XMLSerializer back out — no regex over the markup, so namespaces, CDATA and entities survive. The XML prolog and any DOCTYPE do not survive the round trip, worth 60-110 bytes on an editor export |
| Precision | 0 to 4 decimal places, 2 by default; transform matrices keep 3 more. Path data is re-tokenised and re-emitted with the separators the grammar allows you to drop, so M 24.00000,64.500000 becomes M24 64.5 |
| Correctness check | Both files rasterized into a 256 px box and compared with alpha premultiplied; anything over 2/255 on a channel counts as a differing pixel, and the count is shown |
| Measured on real files | A 1,885-byte Illustrator export drops to 445 bytes — 76.4%, or 874 to 293 gzipped. Across 22 SVGs taken from two production sites the median was 7.5% and the best 23.8% |
| Limits | 2 MB per file, one at a time. Above 300 kB the per-pass byte column switches off, because attributing each pass means running the whole pipeline once per pass |
| Left alone | Text inside text, tspan, title, desc and style; ids on symbol and view; every id when a script element is present; style attributes when the file has a style element, since an attribute loses a cascade fight a style attribute would have won |
Frequently asked questions
Is this SVGO?
No, and the difference is worth knowing before you trust the output. SVGO is a Node build tool with about 50 plugins; this page is 10 passes written against the browser's DOMParser with no dependency at all, and each names the SVGO plugin it corresponds to. What is here: removeComments, removeMetadata, removeEditorsNSData, removeTitle and removeDesc as an option, convertStyleToAttrs, a subset of removeUnknownsAndDefaults, convertTransform, cleanupNumericValues, collapseGroups, cleanupIds and removeUselessDefs. What is not: the geometry half of convertPathData (curve fitting, arc conversion, absolute-to-relative rewriting), mergePaths, reusePaths, moveElemsAttrsToGroup, inlineStyles, removeHiddenElems and removeOffCanvasPaths. Those last few are where SVGO finds another 10-20% on a complex illustration, so for a build pipeline run SVGO; for one file you want to understand, this shows you the ledger.
Why did my file barely shrink?
Almost always because the bytes are not markup. An SVG exported with a photo or a logo bitmap inside it holds that picture as a base64 data URI, and no markup pass can touch it — on one payment-card logo tested here, 171.0 kB of the 171.5 kB file was a single embedded PNG, which is why the optimizer reported 0.0% and said so explicitly. The other case is a file that has already been through a minifier: 22 production SVGs pulled off two live sites gave a median saving of 7.5%, while a fresh Illustrator export in the same run dropped 76.4%. The dividend is in editor bookkeeping, and it can only be collected once.
It says duplicate attribute, but the icon displays fine on my page.
Because inline in HTML your icon is parsed by the HTML parser, which forgives a repeated attribute by ignoring the second one. A standalone .svg file is served as XML and parsed by an XML parser, where a repeated attribute is fatal and the whole document fails. A real example from a WordPress admin asset: fill="none" written twice on the root element, invisible for as long as the markup stayed pasted into a template, and a blank image the day someone saved it as a file. The parser message here carries the line and column, so it is usually a ten-second fix.
Should I let it remove title and desc?
Not if the graphic carries meaning, which is why that pass is the one switched off here. SVGO's default preset enables removeTitle, and a great many icon sets have shipped with no accessible name because of it: on an element with role="img", or one referenced through aria-labelledby, the title element is the text a screen reader announces, and deleting it leaves an image announced as nothing. A purely decorative shape marked aria-hidden loses nothing by having its title stripped, and a title reading "Layer 1" or "Untitled" is pure editor residue — delete those. The page warns you when the file you loaded points its own aria-labelledby at a title you are about to remove.
How many decimal places are actually safe?
Two is safe for any viewBox in the usual 0-1000 range, because the unit is not a pixel until it is drawn. A coordinate in a 0 0 24 24 viewBox rendered at 24 px means one unit is one pixel, so two decimals resolve a hundredth of a pixel; the same file blown up to a 1024 px hero still resolves a fifth of a pixel. Where it goes wrong is a viewBox in the tens of thousands, a map or a floor plan, where two decimals over 40,000 units is a genuine displacement — and transform matrices, whose values are ratios rather than lengths, which is why they keep three digits more than the slider says. Rather than trust any of that, watch the pixel count under the two previews: it is measured, not assumed.
Will it break my gradients, filters or sprite sheet?
Not inside the file: every url(#name), href="#name", aria-labelledby, and animation begin="other.click" is collected before a single id is touched, and anything referenced from a style or script element is collected too. Ids on symbol and view elements are never removed, because that is what a sprite is made of, and the whole id pass stands down when the file contains a script. The genuine risk is a reference from outside the file — an icon fetched as sprite.svg#cart, or a stylesheet on your page selecting #logo-mark. No optimizer reading one file in isolation can see those, so if the SVG is a sprite consumed by other documents, switch that pass off.
Why is the output one long line, and should I gzip it as well?
The line breaks and indentation between tags are removable because they are text nodes that render nothing, and gzip on top of that is worth another third again. Characters inside text, tspan, title, desc and style are left byte for byte alone — that whitespace is content, and a minifier that collapses it moves your typography. The gzip figure shown here is real, computed by CompressionStream in the tab rather than estimated: the sample export starts at 1,885 bytes raw and 874 gzipped, and finishes at 445 raw and 293 gzipped. Since your CDN gzips anyway, that last number is the one that reaches a visitor.
About what actually makes an SVG heavy
Open an icon straight out of a drawing program and most of it is not the drawing. A typical Illustrator export opens with a generator comment, an rdf metadata block naming a document title nobody set, an x and y of 0px on the root, a version attribute the SVG 2 specification dropped, and an enable-background declaration for a filter property no browser ever shipped. Inkscape adds a sodipodi:namedview carrying the zoom level and cursor position of the last person to open the file. Figma and Sketch leave layer names in data-name attributes. On the 1,885-byte sample loaded by the button above, all of that plus one unreferenced gradient and nine decimal places of coordinate accounts for 1,440 bytes — 76.4% of the file, none of it geometry, and it comes off before a single curve is touched.
The passes that get interesting are the ones with a rule behind them. A presentation attribute like fill="red" and a declaration like style="fill:red" paint the same pixels but occupy different levels of the cascade, and the attribute sits below every CSS rule while the style attribute sits above them — so converting one into the other is only safe in a file with no style element, which is why this page checks and says so rather than doing it anyway. Removing fill-opacity="1" is safe only because that property inherits and no ancestor set something else, so each removal here is checked against the element’s ancestors first. And an id is removable only when nothing references it — from url(), from an href fragment, from aria-labelledby, from an animation’s begin attribute, or from CSS. The regex-based "SVG minifiers" that trip over exactly this are easy to spot: they collapse the whitespace inside a text element and silently reflow the words, or they strip an id that a favicon set or an external sprite reference was relying on. Tree surgery on a parsed document cannot make either mistake.
What optimizing cannot do is change what kind of file you have. An SVG whose weight is a base64 bitmap inside an image element is a PNG in fancy dress, and every pass here will report honestly that it found nothing: the answer is to ship the bitmap as a file, or to trace it into real paths if the artwork is flat enough to survive tracing. In the other direction, a cleaned SVG is the best possible input to a rasterizer, so run it through here before SVG to PNG and any missing font or external bitmap becomes visible in the markup rather than in the output. The same file feeds the icon pipeline: the ICO converter and image to ICO both rasterize whatever you hand them, so residue in the vector costs nothing there but confusion, while an avatar generator emitting one SVG per user is a case where 400 saved bytes are 400 bytes multiplied by every profile on the page.
What happens to the file you drop here
Every number on this page is worked out by JavaScript running in the tab you are reading it in. Nothing you type, paste or open is uploaded, logged or kept, which is also why the tools carry on working after you disconnect from the network.
Rendering the before and after side by side means handing each version to an image element, so both get a temporary in-memory address that no other page can reach and that is released once the comparison is drawn. The gzip figure is produced by CompressionStream in the same tab — nothing is sent anywhere to be measured.