SVG Path Data API: Complete Guide for Web Developers 2026
Guide · June 2026

SVG Path Data API:
The Complete Guide for Web Developers

Firefox 137 just shipped the SVG Path Data API — a native way to read, construct, and manipulate SVG paths programmatically in JavaScript. Learn getPathData(), setPathData(), and getPathSegmentAtLength() with real code examples and practical use cases.

Oleg Maximov June 4, 2026 18 min read

Introduction

For over two decades, manipulating SVG paths in JavaScript meant one thing: parsing and constructing strings. Every <path d="M10 10 L 20 20..."> required you to split the d attribute, tokenise commands, handle implicit commands, and then reassemble everything — all through string concatenation. It was fragile, error-prone, and completely opaque to type checking.

The SVG Path Data API (shipped in Firefox 137+, W3C Editor's Draft September 2025) changes this completely. It introduces three methods on SVGPathElementgetPathData(), setPathData(), and getPathSegmentAtLength() — that expose SVG path data as structured JavaScript objects instead of opaque strings.

In this guide, I'll walk you through everything you need to know: the API surface with full syntax, practical code examples, real-world use cases, browser support, polyfill strategies, and the pitfalls to avoid.

The Problem: SVG Path Strings Are Fragile

Before the Path Data API, any SVG path manipulation involved working with the d attribute — a string like "M 10 10 L 20 20 L 30 10 Z". To modify a path programmatically, you had to:

  1. Read element.getAttribute('d') to get the raw string
  2. Tokenise it — split by spaces/commas while preserving negative numbers
  3. Identify command letters (M, L, C, Q, A, Z, etc.) and their coordinates
  4. Detect implicit commands (consecutive L after M without the letter)
  5. Modify the segment you care about
  6. Reassemble everything back into a string
  7. Call element.setAttribute('d', newString)

Any mistake in the parser — a missed implicit command, wrong number of bezier control points, or a missing space before a negative number — would silently break the entire SVG rendering. Debugging meant staring at long strings of numbers. This is a solved-class-of-problem that should have had a native API years ago.

💡 The Takeaway: String-based SVG path manipulation is error-prone, hard to debug, and completely unsuitable for complex path operations like morphing animations or interactive editors. The Path Data API fixes this with structured, typed data.

API Overview

The SVG Path Data API adds three methods to SVGPathElement, each solving a specific need in the path manipulation workflow:

Method Purpose Returns
getPathData() Read all path segments as structured objects PathDataSegment[]
setPathData(segments) Replace path with a new array of segments undefined
getPathSegmentAtLength(distance) Find which segment is at a given length along the path PathDataSegment | null

The PathDataSegment Type

Every segment returned by the API follows the same structure:

interface PathDataSegment {
  type: string;     // Command letter: 'M', 'L', 'C', 'Q', 'A', 'Z', etc.
  values: number[]; // Coordinates for this command
}

The type property uses the same one or two-letter command codes as the SVG path string syntax: M (moveto), L (lineto), C (cubic bezier curve), Q (quadratic bezier), A (arc), Z (closepath). Lowercase means relative coordinates; uppercase means absolute.

The values array contains exactly the right number of coordinates for the command type:

Command Type Values Description
M, m Moveto [x, y] Move to point
L, l Lineto [x, y] Draw a straight line
H, h Horizontal lineto [x] Horizontal line
V, v Vertical lineto [y] Vertical line
C, c Cubic bezier [x1, y1, x2, y2, x, y] Two control points + end point
S, s Smooth cubic bezier [x2, y2, x, y] Reflected control point + end point
Q, q Quadratic bezier [x1, y1, x, y] One control point + end point
T, t Smooth quadratic bezier [x, y] Reflected control point + end point
A, a Arc [rx, ry, xAxisRot, largeArcFlag, sweepFlag, x, y] Elliptical arc segment
Z, z Closepath [] Close the current subpath

Reading Path Data with getPathData()

The getPathData() method is the simplest of the three. Call it on any <path> element and you get a clean array of segment objects:

const path = document.querySelector('svg path');

// The old way — fragile string parsing
const oldD = path.getAttribute('d');
// "M10 80 Q 52.5 10, 95 80 T 180 80 Z"

// The new way — structured data
const segments = path.getPathData();
console.log(segments);
// [
//   { type: 'M', values: [10, 80] },
//   { type: 'Q', values: [52.5, 10, 95, 80] },
//   { type: 'T', values: [180, 80] },
//   { type: 'Z', values: [] }
// ]

Each segment is a clean object with explicit type and values. No more guessing whether "10-20" means [10, -20] or [10, 20]. No more writing your own SVG path tokeniser.

Segment Counting and Analysis

With structured data, analysing a path is trivial:

function analyzePath(pathElement) {
  const segments = pathElement.getPathData();

  return {
    totalSegments: segments.length,
    commandTypes: segments.reduce((acc, s) => {
      acc[s.type] = (acc[s.type] || 0) + 1;
      return acc;
    }, {}),
    boundingBox: calculateBoundingBox(segments),
    isClosed: segments[segments.length - 1]?.type === 'Z'
  };
}

// Get the bounding box from segment coordinates
function calculateBoundingBox(segments) {
  let minX = Infinity, minY = Infinity;
  let maxX = -Infinity, maxY = -Infinity;

  for (const seg of segments) {
    const { type, values } = seg;
    if (type === 'Z' || type === 'z') continue;

    // Extract end-point coordinates based on command type
    const endIdx = getEndCoordinateIndex(type);
    if (endIdx >= 0) {
      const x = values[endIdx - 1];
      const y = values[endIdx];
      minX = Math.min(minX, x);
      minY = Math.min(minY, y);
      maxX = Math.max(maxX, x);
      maxY = Math.max(maxY, y);
    }
  }

  return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
}

function getEndCoordinateIndex(type) {
  const ends = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 6 };
  return ends[type.toUpperCase()] - 1;
}

Before this API, extracting bounding box information from SVG paths required parsing the d string with regex — and every regex was a fragile approximation. Now it's a straightforward array walk.

Modifying Paths with setPathData()

The real power of the API comes with setPathData(). Instead of building a string, you pass an array of PathDataSegment objects to update the path's geometry:

const path = document.querySelector('svg path');

// Read existing path
const segments = path.getPathData();

// Modify the second segment — move its end point
segments[1] = {
  type: 'L',
  values: [150, 200]
};

// Write back — no string concatenation needed
path.setPathData(segments);

This is vastly safer than string manipulation. Each segment is an isolated object. Changing one segment can't accidentally corrupt the rest of the path — unlike string concatenation where a missing space or extra character breaks everything.

Constructing Paths from Scratch

You can also build paths entirely in JavaScript without touching a string:

function createStarPath(cx, cy, outerR, innerR, points) {
  const segments = [];
  const step = Math.PI / points;

  for (let i = 0; i < points * 2; i++) {
    const r = i % 2 === 0 ? outerR : innerR;
    const angle = i * step - Math.PI / 2;
    const x = cx + r * Math.cos(angle);
    const y = cy + r * Math.sin(angle);

    if (i === 0) {
      segments.push({ type: 'M', values: [x, y] });
    } else {
      segments.push({ type: 'L', values: [x, y] });
    }
  }

  segments.push({ type: 'Z', values: [] });
  return segments;
}

// Create a 5-pointed star
const starSegments = createStarPath(100, 100, 80, 40, 5);
pathElement.setPathData(starSegments);

This function generates a perfectly scaled star path using only structured data. No string building, no escaping, no edge cases with negative numbers — just clean arithmetic and object construction.

Transforming Path Coordinates

A common use case is scaling or translating path coordinates. With the old API, you had to parse the string, extract all coordinates, modify them, and rebuild the string. With the Path Data API:

function scalePath(pathElement, scaleX, scaleY) {
  const segments = pathElement.getPathData();

  for (const seg of segments) {
    if (seg.type === 'Z' || seg.type === 'z') continue;

    for (let i = 0; i < seg.values.length; i++) {
      if (i % 2 === 0) {
        seg.values[i] *= scaleX;  // X coordinate
      } else {
        seg.values[i] *= scaleY;  // Y coordinate
      }
    }
  }

  pathElement.setPathData(segments);
}

// Scale a path by 1.5x horizontally
scalePath(myPath, 1.5, 1);

The alternating X/Y pattern in the values array follows SVG convention: even indices (0, 2, 4...) are X coordinates, odd indices (1, 3, 5...) are Y coordinates. This holds for all command types.

Path Morphing and Shape Animation

One of the most exciting use cases is path morphing — smoothly animating from one shape to another. The Path Data API makes this dramatically simpler because both the source and target shapes are arrays of structured objects with the same format:

function morphPath(pathElement, targetSegments, duration = 1000) {
  const startSegments = pathElement.getPathData();

  // Validate segment count matches
  if (startSegments.length !== targetSegments.length) {
    console.warn('Segment count mismatch — morphing may look wrong');
  }

  const startTime = performance.now();

  function animate(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    const eased = easeInOutCubic(progress);

    const currentSegments = startSegments.map((start, i) => {
      if (i >= targetSegments.length) return start;

      const target = targetSegments[i];
      const values = start.values.map((v, j) => {
        const targetVal = target.values[j] ?? v;
        return v + (targetVal - v) * eased;
      });

      return { type: target.type, values };
    });

    pathElement.setPathData(currentSegments);

    if (progress < 1) {
      requestAnimationFrame(animate);
    }
  }

  requestAnimationFrame(animate);
}

function easeInOutCubic(t) {
  return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}

// Usage: morph a square into a circle
const square = [
  { type: 'M', values: [50, 50] },
  { type: 'L', values: [150, 50] },
  { type: 'L', values: [150, 150] },
  { type: 'L', values: [50, 150] },
  { type: 'Z', values: [] }
];

const circle = [
  { type: 'M', values: [100, 50] },
  { type: 'C', values: [127.6, 50, 150, 72.4, 150, 100] },
  { type: 'C', values: [150, 127.6, 127.6, 150, 100, 150] },
  { type: 'C', values: [72.4, 150, 50, 127.6, 50, 100] },
  { type: 'C', values: [50, 72.4, 72.4, 50, 100, 50] },
  { type: 'Z', values: [] }
];

morphPath(myPath, circle, 2000);

💡 Key Insight: For smooth morphing, both paths should have the same number of segments and the same command types at corresponding positions. When this isn't possible, add intermediate steps or normalise the segments before morphing. Always validate segment counts.

Point Evaluation with getPathSegmentAtLength()

The third method, getPathSegmentAtLength(distance), answers a question that previously required complex geometry libraries: "What segment of the path is at this distance from the start?"

const path = document.querySelector('svg path');
const totalLength = path.getTotalLength();

// Find which segment is at 50% of the path length
const midpoint = path.getPathSegmentAtLength(totalLength / 2);
console.log(midpoint);
// { type: 'C', values: [x1, y1, x2, y2, x, y] }

This is invaluable for:

Progressive Path Drawing

Here's a practical example that draws a path segment by segment:

async function progressiveDraw(pathElement, durationPerSegment = 300) {
  const segments = pathElement.getPathData();

  // Start with just the first moveto
  const partialSegments = [segments[0]];
  pathElement.setPathData(partialSegments);

  for (let i = 1; i < segments.length; i++) {
    if (segments[i].type === 'Z' || segments[i].type === 'z') {
      partialSegments.push(segments[i]);
      pathElement.setPathData(partialSegments);
      break;
    }

    partialSegments.push(segments[i]);
    pathElement.setPathData(partialSegments);

    await new Promise(r => setTimeout(r, durationPerSegment));
  }
}

Each segment is added incrementally, creating an animated drawing effect. Combined with getPathSegmentAtLength(), you could also tie the segment reveal to a scroll position or a scrub control.

Data Visualisation with Generated Paths

The Path Data API is a natural fit for programmatic charting and data visualisation. Here's a simple line chart generator:

function createLineChart(dataPoints, width, height, padding = 20) {
  if (dataPoints.length < 2) return [];

  const xScale = (width - 2 * padding) / (dataPoints.length - 1);
  const yMin = Math.min(...dataPoints);
  const yMax = Math.max(...dataPoints);
  const yRange = yMax - yMin || 1;

  const segments = [];

  for (let i = 0; i < dataPoints.length; i++) {
    const x = padding + i * xScale;
    const y = height - padding - ((dataPoints[i] - yMin) / yRange) * (height - 2 * padding);

    if (i === 0) {
      segments.push({ type: 'M', values: [x, y] });
    } else {
      // Use smooth curves instead of straight lines
      const prevX = padding + (i - 1) * xScale;
      const prevY = height - padding - ((dataPoints[i - 1] - yMin) / yRange) * (height - 2 * padding);
      const cpx1 = prevX + xScale * 0.5;
      const cpx2 = x - xScale * 0.5;

      segments.push({
        type: 'C',
        values: [cpx1, prevY, cpx2, y, x, y]
      });
    }
  }

  return segments;
}

// Usage
const data = [10, 45, 30, 70, 55, 90, 85];
const chartSegments = createLineChart(data, 400, 200);
chartPath.setPathData(chartSegments);

This generates a smooth bezier curve chart from raw data. The cubic bezier control points create natural-looking curves between data points. With the old string-based approach, generating the command string for something like this required careful concatenation and was prone to missing commas or spaces.

Interactive Path Editor

The most ambitious use case is an interactive SVG path editor. With getPathData() and setPathData(), you can build a drag-to-modify path editor in surprisingly few lines:

class PathEditor {
  constructor(pathElement) {
    this.path = pathElement;
    this.segments = pathElement.getPathData();
    this.dragging = null;
    this.bindEvents();
  }

  getPointPositions() {
    // Extract all endpoint coordinates as manipulable points
    const points = [];
    for (const seg of this.segments) {
      if (seg.type === 'Z' || seg.type === 'z') continue;
      const endIdx = [6, 4, 2].find(i => seg.values.length >= i) ?? 2;
      if (endIdx >= 2) {
        points.push({
          segIndex: this.segments.indexOf(seg),
          valIndexX: endIdx - 2,
          valIndexY: endIdx - 1,
          x: seg.values[endIdx - 2],
          y: seg.values[endIdx - 1]
        });
      }
    }
    return points;
  }

  bindEvents() {
    const svg = this.path.closest('svg');

    svg.addEventListener('mousedown', (e) => {
      const rect = svg.getBoundingClientRect();
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;

      // Find closest point to click
      const points = this.getPointPositions();
      let minDist = Infinity;
      for (const p of points) {
        const dist = Math.hypot(p.x - mx, p.y - my);
        if (dist < minDist && dist < 10) {
          minDist = dist;
          this.dragging = p;
        }
      }
    });

    svg.addEventListener('mousemove', (e) => {
      if (!this.dragging) return;

      const rect = svg.getBoundingClientRect();
      const seg = this.segments[this.dragging.segIndex];

      if (seg) {
        seg.values[this.dragging.valIndexX] = e.clientX - rect.left;
        seg.values[this.dragging.valIndexY] = e.clientY - rect.top;
        this.path.setPathData(this.segments);
      }
    });

    svg.addEventListener('mouseup', () => {
      this.dragging = null;
    });
  }
}

// Usage
const editor = new PathEditor(document.querySelector('svg path'));

This is a fully functional path editor in under 60 lines. The key insight is that setPathData() accepts the same format as getPathData() returns — so the drag handler simply modifies the values array and writes it back. With the old string API, each drag event would require a full path string rebuild.

Browser Support and Feature Detection

As of June 2026, the SVG Path Data API has limited browser support:

Feature Detection

Always check for support before using the API:

function supportsPathDataAPI() {
  const path = document.createElementNS(
    'http://www.w3.org/2000/svg', 'path'
  );
  return 'getPathData' in path;
}

if (supportsPathDataAPI()) {
  // Use the native Path Data API
  const segments = myPath.getPathData();
} else {
  // Fallback: parse d-attribute manually
  const dString = myPath.getAttribute('d');
  const segments = parsePathString(dString);
}

Polyfill Strategy

For production use, implement a polyfill that wraps the old string API. A basic polyfill can parse the d attribute into segments for reading and convert segments back to a string for writing:

// Minimal polyfill concept — expand for production use
function polyfillPathData() {
  if (supportsPathDataAPI()) return;

  SVGPathElement.prototype.getPathData = function() {
    return parseDString(this.getAttribute('d') || '');
  };

  SVGPathElement.prototype.setPathData = function(segments) {
    this.setAttribute('d', segmentsToDString(segments));
  };
}

function segmentsToDString(segments) {
  return segments.map(s => {
    if (s.type === 'Z' || s.type === 'z') return 'Z';
    return s.type + ' ' + s.values.join(' ');
  }).join(' ');
}

// Apply polyfill before using the API
polyfillPathData();

⚠️ Production Note: The polyfill above is simplified for illustration. A production polyfill needs to handle implicit repeated commands (e.g., "M 10 10 L 20 20 30 30"), arc flags (which are not separated by commas in the string format), and edge cases like scientific notation. Consider using a community-maintained polyfill for production.

Limitations and Considerations

The SVG Path Data API is powerful, but it's important to understand its current limitations:

Summary

The SVG Path Data API is a long-overdue addition to the web platform that finally brings structured data manipulation to SVG paths. It replaces two decades of fragile string-parsing workarounds with clean, typed JavaScript objects.

The key takeaways:

Start experimenting with the SVG Path Data API today. Even with current browser limitations, the API is stable enough for experimental projects, and the polyfill approach works well for production use while other browsers catch up.

Want to build interactive SVG visualisations? I'm available for frontend architecture, SVG/Canvas development, and data visualisation consulting. View my services or contact me directly.

FAQ

What is the SVG Path Data API?
The SVG Path Data API provides three methods on SVGPathElement — getPathData(), setPathData(), and getPathSegmentAtLength() — that let you read, modify, and construct SVG path data programmatically in JavaScript. It replaces fragile SVG path string parsing with a structured array of path segment objects.
Which browsers support the SVG Path Data API?
As of June 2026, the SVG Path Data API is only supported in Firefox 137+. Chrome, Edge, and Safari do not yet implement it. The W3C specification is at Editor's Draft stage (September 2025). Use feature detection and fall back to SVG path string manipulation in unsupported browsers.
What does getPathData() return?
getPathData() returns an array of PathDataSegment objects. Each segment has a type string (e.g., 'M' for moveto, 'L' for lineto, 'C' for cubic bezier) and a values array containing the numeric coordinates for that command. For example, a 'M' segment has values [x, y], while a 'C' segment has values [x1, y1, x2, y2, x, y].
How is setPathData() better than setting d-string directly?
setPathData() accepts a structured array of segment objects instead of a string. This eliminates string concatenation bugs, improves performance by avoiding internal SVG path parsing, and enables type-safe programmatic path construction. You can pass the same array format returned by getPathData(), making path transformation code cleaner and more predictable.
What can you build with the SVG Path Data API?
The API enables several powerful use cases: (1) SVG icon morphing and shape animation with JavaScript, (2) programmatic path generation for data visualisation and charting, (3) interactive SVG path editors with real-time manipulation, (4) SVG path analysis tools using getPathSegmentAtLength() for point evaluation, and (5) parametrised SVG graphics that respond to data or user input.
Does the SVG Path Data API handle relative and absolute commands?
Yes. The type property of each PathDataSegment reflects the original command — lowercase for relative (e.g., 'm', 'l', 'c') and uppercase for absolute (e.g., 'M', 'L', 'C'). When you pass segments to setPathData(), you can mix relative and absolute commands freely. The API preserves the semantics of each command type during rendering.
How do you detect SVG Path Data API support?
Use the in operator on an SVGPathElement instance: 'getPathData' in document.createElementNS('http://www.w3.org/2000/svg', 'path'). If true, the API is available. Always use feature detection and provide graceful fallback — parse SVG d-attribute strings manually in non-supporting browsers, or use a polyfill that converts the old string format to the new segment format.
Contact

Building interactive SVG visualisations?

I build data-driven web applications with SVG, Canvas, and modern JavaScript. Let's discuss your project — free consultation.