svg-path-parser and svgpath are both JavaScript libraries designed to parse, normalize, and transform SVG path data strings into structured formats that can be processed programmatically. These tools are essential when building vector graphics editors, animation systems, or any application that needs to interpret or modify SVG <path> elements at runtime. While both address the same core problem — converting raw SVG path syntax like 'M10 10 L20 20' into actionable data — they differ significantly in architecture, mutability approach, feature scope, and extensibility.
Both svg-path-parser and svgpath help developers work with SVG path data — those cryptic strings like "M10,10 L20,30 Q40,50 60,70" inside <path d="..."> elements. But they solve the problem in very different ways. Let’s break down how they handle real-world tasks.
svg-path-parser treats path data as read-only input. It parses the string once and returns a plain array of normalized command objects. No modifications allowed after parsing.
// svg-path-parser: immutable output
import parse from 'svg-path-parser';
const commands = parse('M10 10L20 20');
// Returns:
// [
// { code: 'M', command: 'moveto', x: 10, y: 10 },
// { code: 'L', command: 'lineto', x: 20, y: 20 }
// ]
svgpath gives you a mutable object you can chain methods on. Parsing is just the first step — you then transform, round, or serialize the path in place.
// svgpath: chainable transformations
import SVGPath from 'svgpath';
const result = new SVGPath('M10 10L20 20')
.scale(2)
.round(1)
.toString();
// Returns: "M20 20L40 40"
svg-path-parser returns a flat array of plain JavaScript objects. Each object includes both the short code ('M') and full command name ('moveto'), plus parsed coordinates. This makes it easy to iterate, filter, or feed into other systems without hidden state.
const parsed = parse('M0 0 C10 10 20 20 30 30');
console.log(parsed[1]);
// {
// code: 'C',
// command: 'curveto',
// x1: 10, y1: 10,
// x2: 20, y2: 20,
// x: 30, y: 30
// }
svgpath doesn’t expose internal structure directly. You interact via methods like .abs() (convert to absolute coords) or .rel() (relative), and only get the final string via .toString(). If you need access to individual segments, you must parse externally or use undocumented internals — which isn’t recommended.
svg-path-parser does one thing: parse. Want to scale the path? Rotate it? You’ll need to write your own logic or pair it with a geometry library.
// With svg-path-parser, you handle transforms manually
const scaled = parsed.map(cmd => ({
...cmd,
x: cmd.x * 2,
y: cmd.y * 2,
// ...handle control points too
}));
svgpath includes common operations out of the box:
.scale(sx, sy).translate(x, y).rotate(angle, cx, cy).matrix(a, b, c, d, e, f).round(precision).abs() / .rel()This saves significant effort in apps that frequently adjust paths (e.g., drag-to-resize in a vector editor).
Both libraries normalize path syntax, but differently.
svg-path-parser converts all commands to their canonical form and ensures consistent property names. For example, horizontal/vertical lines (H, V) become generic L commands with full (x, y) coordinates. This simplifies downstream processing.
svgpath preserves original command types unless you explicitly call .unshort() or .abs(). By default, H10 stays H10 — which may be desirable if you care about minimizing output size or retaining author intent.
If you parse a path and immediately serialize it back:
svg-path-parser cannot serialize — you’d need a separate formatter.svgpath maintains high fidelity by default, but note: calling .abs() or .round() changes the output permanently. There’s no “undo” — it’s a one-way transformation pipeline.svg-path-parser throws clear errors on invalid syntax (e.g., missing coordinates). This helps catch malformed paths early during development.
svgpath is more permissive. It may silently skip malformed segments or produce unexpected results. You’ll need extra validation if input comes from untrusted sources.
Despite differences, both libraries agree on fundamentals:
M, L, C, Q, A, Z, etc.// Both parse this valid path
'M10,10 l5,0 q2,5 4,0 z'
// All equivalent:
'M10 10'
'M10,10'
'M 10 10'
| Feature | svg-path-parser | svgpath |
|---|---|---|
| Primary Use Case | Read-only parsing | Parse + transform + serialize |
| Mutability | Immutable output | Mutable, chainable object |
| Transformations | None built-in | Scale, rotate, translate, round, etc. |
| Output Access | Direct array of command objects | Only via .toString() |
| Normalization | Aggressive (e.g., H → L) | Conservative (preserves original cmds) |
| Serialization | Not supported | Built-in |
| Error Strictness | Throws on invalid input | Tolerant, may skip bad segments |
Use svg-path-parser when you’re building a renderer, analyzer, or converter that only needs to read path data. Think: exporting SVG to canvas, calculating path length, or generating previews. Its simplicity and immutability reduce bugs in data-flow-heavy apps.
Use svgpath when you’re building an editor, optimizer, or transformer that needs to modify paths. Think: a design tool that scales icons, a build plugin that minifies SVGs, or an animation system that morphs paths. Its built-in utilities cut down on custom math code.
Both are mature, stable choices — pick based on whether your workflow stops at parsing or continues into transformation.
Choose svg-path-parser if you need a lightweight, immutable parser that strictly focuses on converting SVG path strings into a clean, normalized array of command objects. It’s ideal for read-only scenarios like rendering paths in canvas, analyzing path geometry, or feeding data into visualization pipelines where side effects must be avoided. Its functional design aligns well with modern reactive frameworks like React or Svelte.
Choose svgpath if you require an all-in-one toolkit for not just parsing but also transforming, optimizing, and serializing SVG paths. It supports in-place mutations like scaling, rotating, or rounding coordinates, making it suitable for interactive editors, export utilities, or build-time optimization tools. Its chainable API and built-in normalization features reduce boilerplate when manipulating paths dynamically.
An SVG path parser, originally built from the PEG.js grammar specified here, published as an NPM module.
Grammar originally written by Gavin Kistner.
require('svg-path-parser')(d)Takes an SVG path string. The following code…
var parseSVG = require('svg-path-parser');
var d='M3,7 5-6 L1,7 1e2-.4 m-10,10 l10,0 \
V27 89 H23 v10 h10 \
C33,43 38,47 43,47 c0,5 5,10 10,10 \
S63,67 63,67 s-10,10 10,10 \
Q50,50 73,57 q20,-5 0,-10 \
T70,40 t0,-15 \
A5,5 45 1,0 40,20 a5,5 20 0,1 -10-10 Z';
console.log(parseSVG(d));
…will yield an array of commands that define the path, like so:
[ { code:'M', command:'moveto', x:3, y:7 },
{ code:'L', command:'lineto', x:5, y:-6 },
{ code:'L', command:'lineto', x:1, y:7 },
{ code:'L', command:'lineto', x:100, y:-0.4 },
{ code:'m', command:'moveto', relative:true, x:-10, y:10 },
{ code:'l', command:'lineto', relative:true, x:10, y:0 },
{ code:'V', command:'vertical lineto', y:27 },
{ code:'V', command:'vertical lineto', y:89 },
{ code:'H', command:'horizontal lineto', x:23 },
{ code:'v', command:'vertical lineto', relative:true, y:10 },
{ code:'h', command:'horizontal lineto', relative:true, x:10 },
{ code:'C', command:'curveto', x1:33, y1:43, x2:38, y2:47, x:43, y:47 },
{ code:'c', command:'curveto', relative:true, x1:0, y1:5, x2:5, y2:10, x:10, y:10 },
{ code:'S', command:'smooth curveto', x2:63, y2:67, x:63, y:67 },
{ code:'s', command:'smooth curveto', relative:true, x2:-10, y2:10, x:10, y:10 },
{ code:'Q', command:'quadratic curveto', x1:50, y1:50, x:73, y:57 },
{ code:'q', command:'quadratic curveto', relative:true, x1:20, y1:-5, x:0, y:-10 },
{ code:'T', command:'smooth quadratic curveto', x:70, y:40 },
{ code:'t', command:'smooth quadratic curveto', relative:true, x:0, y:-15 },
{ code:'A', command:'elliptical arc', rx:5, ry:5, xAxisRotation:45, largeArc:true, sweep:false, x:40, y:20 },
{ code:'a', command:'elliptical arc', relative:true, rx:5, ry:5, xAxisRotation:20, largeArc:false, sweep:true, x:-10, y:-10 },
{ code:'Z', command:'closepath' } ]
Alternatively, from version 1.1 on, the module exports multiple functions that you can separately use:
const {parseSVG, makeAbsolute} = require('svg-path-parser');
Version 1.1 adds the ability to convert an array of path commands into their absolute-coordinate equivalents. This modifies the parsed command objects in place, and also returns the array of commands. Continuing the example above:
const {parseSVG, makeAbsolute} = require('svg-path-parser');
const commands = parseSVG(d);
makeAbsolute(commands); // Note: mutates the commands in place!
console.log(commands);
[ { code:'M', command:'moveto', x0:0, y0:0 x:3, y:7 },
{ code:'L', command:'lineto', x0:3, y0:7 x:5, y:-6 },
{ code:'L', command:'lineto', x0:5, y0:-6 x:1, y:7 },
{ code:'L', command:'lineto', x0:1, y0:7 x:100, y:-0.4 },
{ code:'M', command:'moveto', x0:100, y0:-0.4 x:90, y:9.6 },
{ code:'L', command:'lineto', x0:90, y0:9.6 x:100, y:9.6 },
{ code:'V', command:'vertical lineto', x0:100, y0:9.6, x:100, y:27 },
{ code:'V', command:'vertical lineto', x0:100, y0:27, x:100, y:89 },
{ code:'H', command:'horizontal lineto', x0:100, y0:89, x:23, y:89 },
{ code:'V', command:'vertical lineto', x0:23, y0:89, y:99, x:23 },
{ code:'H', command:'horizontal lineto', x0:23, y0:99, x:33, y:99 },
{ code:'C', command:'curveto', x0:33, y0:99 x1:33, y1:43, x2:38, y2:47, x:43, y:47 },
{ code:'C', command:'curveto', x0:43, y0:47 x1:43, y1:52, x2:48, y2:57, x:53, y:57 },
{ code:'S', command:'smooth curveto', x0:53, y0:57 x2:63, y2:67, x:63, y:67 },
{ code:'S', command:'smooth curveto', x0:63, y0:67 x2:53, y2:77, x:73, y:77 },
{ code:'Q', command:'quadratic curveto', x0:73, y0:77 x1:50, y1:50, x:73, y:57 },
{ code:'Q', command:'quadratic curveto', x0:73, y0:57 x1:93, y1:52, x:73, y:47 },
{ code:'T', command:'smooth quadratic curveto', x0:73, y0:47 x:70, y:40 },
{ code:'T', command:'smooth quadratic curveto', x0:70, y0:40 x:70, y:25 },
{ code:'A', command:'elliptical arc', x0:70, y0:25 rx:5, ry:5, xAxisRotation:45, largeArc:true, sweep:false, x:40, y:20 },
{ code:'A', command:'elliptical arc', x0:40, y0:20 rx:5, ry:5, xAxisRotation:20, largeArc:false, sweep:true, x:30, y:10 },
{ code:'Z', command:'closepath', x0:30, y0:10, x:90, y:9.6 } ]
In addition to converting all commands to absolute coordinates, the makeAbsolute function ensures that:
x0 and y0 properties showing the start point for the command.x and y properties showing the finish point for the command.
H, V, and Z commands equivalent to an L command.makeAbsolute(cmds)..x) are no longer in a .args array of values, but are instead part
of the command object itself.moveto command is always absolute.This library is released under an MIT-style license. That generally means that you are free to do almost anything you want with it as long as you give a bit of credit where credit is due. See the LICENSE file included for the actual legal limitations.