PerfectVector
By Irene Kim10 min read

SVG.js: Import Artwork, Edit a Path, and Export SVG

Import trusted SVG markup into SVG.js, recolor a named path, and check full-root versus children-only exports. Keep original geometry and inspect saved files.

On this page

To import artwork into SVG.js for a part-level edit, pass trusted SVG markup to .svg(), find the intended element by its authored ID, and change that element's fill. Export the artwork's full SVG root for a standalone file. A children-only export is a fragment: it needs a root with the intended dimensions and viewBox before you deliver it as a document.

This guide uses an original hollow diamond and a separate triangle. The triangle changes from ochre to coral while the diamond keeps its teal fill and opening. We tested the complete example below with SVG.js 3.2.6, pinned to the official 3.2.6 release. These observations concern two simple authored paths, not every feature another SVG might contain.

Decide whether you need live shapes or an image

A JavaScript import statement that produces an asset URL answers a loading question. It does not, by itself, put the file's paths into your editor's live SVG tree. SVG.js's current import/export documentation describes importing markup and retrieving either an outer SVG element or its children.

RepresentationWhat our example lets you addressDelivery check
Trusted markup imported into a live SVG rootThe named accent pathInspect the edited root and reopen its export
SVG loaded through an image elementThe image element as a wholeInspect the image payload separately
Children-only markupTwo path elements without the original rootSupply a deliberate root, dimensions and viewBox

SVG inside an image is still SVG content. In our image-wrapper control, however, the containing SVG tree has one image element and no direct paths; searching that tree for accent finds nothing. It does not reach into the embedded image's document. MDN describes the separate SVG image context and its restrictions. Do not infer the editing model from a sharp-looking render.

For the broader embedding decision, read inline SVG versus an image. If you want Fabric's canvas object model instead, the Fabric.js import walkthrough covers a different parser and editing API.

Run one named-path edit and save each representation

Save the complete code below as starter.html in an empty folder. Serve it over HTTP, for example with python3 -m http.server 8000, then open http://localhost:8000/starter.html. The script loads the pinned package from its CDN, so this starter needs a network connection.

The page displays the original artwork, the live edited copy and an image-wrapper control. Its links download the original master, the before/after full roots, the children-only text fragment, a rewrapped copy and the image wrapper. The report reads pixels from SVG images painted to a canvas; it does not export a PNG.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Original SVG.js artwork import and export</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  main { display: grid; grid-template-columns: repeat(3, 260px); gap: 20px; }
  figure { margin: 0; }
  svg, img { display: block; background: white; border: 1px solid #bbb; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Trusted artwork, one fill edit and two export forms</h1>
<main>
  <figure><img id="original" alt="Original artwork"><figcaption>Original SVG image</figcaption></figure>
  <figure><div id="live"></div><figcaption>Imported live SVG, accent edited</figcaption></figure>
  <figure><div id="wrapper"></div><figcaption>Image wrapper, original artwork</figcaption></figure>
</main>
<p id="downloads"></p>
<pre id="report"></pre>
<script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.2.6/dist/svg.min.js"></script>
<script>
  const markup = '<svg xmlns="http://www.w3.org/2000/svg" id="original-artwork" width="240" height="160" viewBox="0 0 240 160">'
    + '<path id="body" d="M80 20 L140 80 L80 140 L20 80 Z M80 56 L104 80 L80 104 L56 80 Z" fill="#176b5b" fill-rule="evenodd"/>'
    + '<path id="accent" d="M172 48 L220 80 L172 112 Z" fill="#d4a53a"/></svg>';
  const url = text => 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(text);
  async function pixels(text) {
    const image = new Image();
    image.src = url(text);
    await image.decode();
    const canvas = document.createElement('canvas');
    canvas.width = 240;
    canvas.height = 160;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0);
    const rgba = (x, y) => [...ctx.getImageData(x, y, 1, 1).data];
    return { naturalWidth: image.naturalWidth, naturalHeight: image.naturalHeight,
      opening: rgba(80, 80), body: rgba(80, 40), accent: rgba(188, 80), corner: rgba(0, 0) };
  }
  function inventory(text) {
    const node = new DOMParser().parseFromString(text, 'image/svg+xml').documentElement;
    return { root: node.localName, viewBox: node.getAttribute('viewBox'),
      paths: [...node.querySelectorAll('path')].map(p => ({ id: p.id, d: p.getAttribute('d'), fill: p.getAttribute('fill') })),
      imageCount: node.querySelectorAll('image').length };
  }
  async function run() {
    document.getElementById('original').src = url(markup);
    const host = SVG().addTo('#live').size(240, 160).viewbox(0, 0, 240, 160);
    host.svg(markup);
    const artwork = host.findOne('#original-artwork');
    const before = artwork.svg();
    const accent = artwork.findOne('#accent');
    const beforeD = accent.attr('d');
    accent.fill('#b95434');
    const full = artwork.svg();
    const children = artwork.svg(false);
    const rewrapped = SVG().size(240, 160).viewbox(0, 0, 240, 160);
    rewrapped.svg(children);
    const wrappedFull = rewrapped.svg();
    const imageWrapper = SVG().addTo('#wrapper').size(240, 160).viewbox(0, 0, 240, 160);
    imageWrapper.image(url(markup)).size(240, 160);
    const wrapperFull = imageWrapper.svg();
    const files = { 'master.svg': markup, 'before.svg': before, 'edited.svg': full,
      'children-only.txt': children, 'rewrapped.svg': wrappedFull, 'image-wrapper.svg': wrapperFull };
    for (const [name, text] of Object.entries(files)) {
      const a = document.createElement('a');
      a.href = name.endsWith('.txt') ? 'data:text/plain;charset=utf-8,' + encodeURIComponent(text) : url(text);
      a.download = name;
      a.textContent = name;
      a.style.marginRight = '12px';
      document.getElementById('downloads').append(a);
    }
    const results = {};
    for (const [name, text] of Object.entries(files)) {
      if (name.endsWith('.svg')) results[name] = { inventory: inventory(text), pixels: await pixels(text) };
    }
    document.getElementById('report').textContent = JSON.stringify({
      requestedPackageVersion: '3.2.6',
      scriptURL: document.querySelector('script[src]').src,
      selectedAccentFound: Boolean(accent), selectedAccentDUnchanged: beforeD === accent.attr('d'),
      allPathDUnchanged: JSON.stringify(inventory(before).paths.map(p => p.d)) === JSON.stringify(inventory(full).paths.map(p => p.d)),
      childrenHasOuterSVG: /<svg[\s>]/i.test(children),
      wrapperDirectPathCount: imageWrapper.find('path').length,
      wrapperAccentFound: Boolean(imageWrapper.findOne('#accent')), results
    }, null, 2);
  }
  run();
</script>
</html>
 

The imported markup contains a nested root named original-artwork. The example selects that root before exporting, so the delivery copy has its authored 240 × 160 viewport rather than relying on an unrelated parent container. Then it selects accent inside that root. SVG.js documents selector-based references and element methods; explicit part names avoid relying on path order.

The code is intentionally limited to its own fixed markup. It is not an SVG upload sanitizer. Parsing or inserting an arbitrary string does not establish that it is safe; MDN's DOMParser reference distinguishes parsing from the risks of inserting untrusted content. Design a separate validation and trust policy before adapting this routine for uploads.

Inspect the saved result, not only the live preview

Our actual browser run produced these checks:

Saved representationPaths in its document treeImagesTriangle fill
Original master20#d4a53a
Full root before editing20#d4a53a
Full root after editing20#b95434
Children placed in a new full root20#b95434
Image wrapper01Original ochre inside its image payload

Both original path d strings remained identical in the before/after exports. The sampled triangle interior changed from RGBA (212, 165, 58, 255) to (185, 84, 52, 255). The sampled diamond interior stayed (23, 107, 91, 255); its opening and the outside corner remained alpha 0. All five standalone SVG files reopened as images with natural dimensions 240 × 160.

Change one named part
Conceptual comparison of matching hollow diamond artwork with only a separate triangle changing from gold to coral
Conceptual illustration of a part-level color edit. Decorative bounds and arrows are diagram cues; the executed starter, saved source inventory and browser pixel checks supply the actual evidence.

Those results establish the intended edit for this fixture. They do not establish byte-identical XML preservation, support for all filters or CSS, or suitability for a manufacturing workflow. The library's serialization can change the document's markup while leaving these particular path strings intact. Keep the original master beside the delivery copy.

For your own art, check the structures you actually use: names and groups, clipping, gradients, transforms, strokes and text. Reopen the exported file in its receiving application, then repeat the intended edit. The SVG editing guide can help diagnose source structure before you change the import code.

Treat a children-only export as a deliberate fragment

In this run, artwork.svg(false) returned two paths with no outer svg tag. It omitted the artwork root's viewport. The separate rewrapped SVG root supplies 240 × 160 dimensions and the same viewBox before accepting those children; its exported file then renders the checked coral triangle.

Choose between a complete document and a fragment based on the destination. For a standalone download, keep the full intended root. For insertion into an existing drawing, decide which parent coordinate system, inherited styles and definitions those children will use. This simple fixture has no external resources or shared definitions, so it does not test whether a fragment containing references carries every dependency it needs.

Our image-wrapper export retained an encoded SVG whose decoded bytes matched the original master. That control shows a representation difference, not a raster conversion. If your source is already usable vector geometry, correct the element selection or export boundary before tracing it again.

When the source has no paths to edit

If your own flat PNG or JPG illustration needs separately recolorable regions, prepare an SVG candidate with PerfectVector. Inspect the contours and openings, then author the part names your application needs. Vectorization does not know that one region should be called accent or guarantee that an imported document fits your editor's data model.

Keep a suitable SVG master when those shapes already exist. Use the vectorization overview to decide whether raster-to-vector preparation belongs upstream of your code at all.

FAQ

Can SVG.js import an SVG string for editing? Yes. This starter imports its own trusted markup into a live SVG container, selects an authored path and changes its fill. Arbitrary uploads need a separate trust and validation policy; the markup API is not a sanitizer.

Why can I see the artwork but not find its accent path? Check whether you imported live markup or placed an image. Our image-wrapper control displays SVG artwork but exposes no direct accent path in the wrapper's tree. Also check the selected parent and the source's actual IDs.

Is a children-only export a complete SVG file? Not in this fixture. It contains two paths without the outer SVG viewport. Use the intended full root for a standalone file, or explicitly provide the destination root and coordinate system when inserting a fragment.

Does a successful edit prove that export preserves every SVG feature? No. We checked two simple paths, their fill values, exact path data and five reopened files. Test the specific features and receiving application your artwork needs, and retain the original master.

Sources

  1. SVG.js — Importing / Exporting, version 3.2 — Markup import and full-root versus children-only serialization.
  2. SVG.js — Referencing / Creating elements, version 3.2 — Finding named elements and changing their properties.
  3. SVG.js — Release 3.2.6 — The official release pinned by the executed starter.
  4. MDN — SVG as an image — Image embedding context and its restrictions.
  5. MDN — DOMParser.parseFromString — Parsing markup and the separate handling of untrusted input.

If your illustration exists only as raster pixels and needs part-level edits, prepare a vector candidate from your own artwork. Check the outline and openings, keep the master, then prove the named edit and saved SVG in your receiving workflow.

More from the blog

PerfectVector

Start with a cleaner SVG
that is easier to edit

No credit card required