ScreenshotNeo

BlogHow-to

HTML to SVG Converter and API

Learn what HTML-to-SVG conversion really means, when to use SVG foreignObject, and how to render HTML as an image with browser code or an API.

By the ScreenshotNeo team29 September 202611 min read

HTML to SVG Converter and API

There are two different things people mean by “HTML to SVG.” One is to place HTML inside an SVG using <foreignObject>. The other is to render a webpage and translate its appearance into native SVG shapes and text. The first is a standards-based embedding technique; it does not turn arbitrary HTML and CSS into portable SVG drawing commands.

If your goal is to preserve editable vector geometry, a foreignObject is not a conversion. If your goal is to package a browser-rendered fragment in an SVG document for a controlled viewer, it may fit. If you only need a visual snapshot, a PNG, JPEG, WebP, or PDF capture is often simpler. This guide explains the distinction, gives a complete standalone SVG example, shows how to create and inspect it with cURL, Python, and Node.js, and covers the rendering constraints that affect production use.

1. Choose the output you actually need

Goal Useful approach What the output contains
Keep HTML markup and CSS associated with an SVG canvas SVG <foreignObject> An SVG element containing a foreign content fragment, commonly XHTML in browsers.
Get true SVG paths and text from a rendered page A converter that explicitly translates supported HTML/CSS features into SVG primitives Native SVG elements, if the chosen converter supports the input features. Validate its documented output; an .svg suffix alone proves nothing.
Save a faithful visual snapshot Browser screenshot A raster image such as PNG, JPEG, or WebP.
Save a paginated document Browser print or PDF capture A PDF with page layout and print behavior.

The W3C describes foreignObject as an extension point for including document fragments from other languages in an SVG document. That is embedding, not a general HTML-to-native-SVG translation. [W3C SVG Integration]

2. Embed HTML in a standalone SVG

The following is a complete SVG file. Save it as card.svg and open it in a browser that supports foreignObject. Its XHTML namespace declaration is needed for XHTML content in a standalone SVG document. When SVG is inline in an HTML document, that declaration can be omitted. [MDN: foreignObject]

The foreignObject rectangle sets the viewport used to lay out its embedded content.
The foreignObject rectangle sets the viewport used to lay out its embedded content.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xhtml="http://www.w3.org/1999/xhtml"
     width="640" height="360" viewBox="0 0 640 360">
  <rect width="640" height="360" rx="20" fill="#f2f5fa"/>
  <foreignObject x="32" y="32" width="576" height="296">
    <xhtml:div xmlns="http://www.w3.org/1999/xhtml"
                style="box-sizing:border-box;width:100%;height:100%;
                       padding:32px;background:white;border-radius:14px;
                       color:#182230;font-family:Arial,sans-serif">
      <h1 style="margin:0 0 12px;font-size:28px">A browser-rendered card</h1>
      <p style="margin:0;line-height:1.5">
        This HTML fragment is embedded inside an SVG viewport.
      </p>
      <a href="https://example.com">A link in the HTML fragment</a>
    </xhtml:div>
  </foreignObject>
</svg>

The foreignObject rectangle establishes the embedded fragment’s viewport. Its width and height affect layout and wrapping much like a div of the same dimensions. Choose explicit dimensions, and keep the outer SVG’s viewBox consistent with the coordinate system you want consumers to see. [W3C SVG Integration]

Inline SVG in an HTML page

If embedding directly in an HTML document, place the SVG in the HTML namespace context. A minimal example is:

<div class="preview">
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360">
    <rect width="640" height="360" fill="#f2f5fa"/>
    <foreignObject x="20" y="20" width="600" height="320">
      <div xmlns="http://www.w3.org/1999/xhtml">
        <h2>Embedded fragment</h2>
        <p>Keep the XHTML namespace explicit when serializing.</p>
      </div>
    </foreignObject>
  </svg>
</div>

For a serialized or standalone SVG, explicitly declaring the XHTML namespace on the embedded root avoids relying on surrounding HTML parsing context. The SVG 2 embedded-content rules also caution that HTML elements placed elsewhere in an SVG subtree may be treated as unknown for rendering; put the foreign content inside foreignObject. [W3C SVG 2: Embedded Content]

3. Generate an SVG wrapper from Python

This runnable standard-library example writes a fixed card fragment into a standalone SVG. It escapes dynamic text so content cannot accidentally break the XML structure. It does not sanitize untrusted HTML; do not replace the fixed fragment with arbitrary user markup without a suitable HTML sanitizer and a deliberate resource policy.

from html import escape
from pathlib import Path

width, height = 640, 360
title = escape("A generated card")
message = escape("Created with Python and an SVG foreignObject.")
svg = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xhtml="http://www.w3.org/1999/xhtml"
     width="{width}" height="{height}" viewBox="0 0 {width} {height}">
  <rect width="100%" height="100%" rx="16" fill="#eef2f8"/>
  <foreignObject x="24" y="24" width="592" height="312">
    <xhtml:div xmlns="http://www.w3.org/1999/xhtml"
       style="box-sizing:border-box;width:100%;height:100%;padding:28px;
              background:white;color:#182230;font:16px Arial,sans-serif">
      <h1 style="font-size:26px">{title}</h1>
      <p>{message}</p>
    </xhtml:div>
  </foreignObject>
</svg>'''
Path("card.svg").write_text(svg, encoding="utf-8")
print("Wrote card.svg")

Run it with python3 make_svg.py, then open card.svg in the target browser. If the target is a vector editor, document previewer, or server-side renderer, test there too: browser support does not establish identical rendering in every consumer.

4. Inspect or fetch an SVG with cURL, Python, and Node.js

These examples retrieve an SVG from a URL you control. They do not convert arbitrary remote HTML. To make an actual converter endpoint, you need a service that documents which HTML/CSS it accepts and whether it embeds markup or emits native SVG. The dossier does not establish a particular commercial HTML-to-SVG conversion API, its fidelity, or pricing.

cURL

curl --fail --location --show-error \
  "https://example.com/card.svg" \
  --output card.svg

# Inspect the response headers and status without saving a file
curl --head --location --show-error "https://example.com/card.svg"

Python

from pathlib import Path
import requests

url = "https://example.com/card.svg"
response = requests.get(url, timeout=(5, 30))
response.raise_for_status()
content_type = response.headers.get("content-type", "")
if "svg" not in content_type and not response.text.lstrip().startswith("<"):
    raise ValueError(f"Expected an SVG response; got {content_type!r}")
Path("card.svg").write_bytes(response.content)
print(f"Saved {len(response.content)} bytes")

Node.js

import { writeFile } from "node:fs/promises";

const response = await fetch("https://example.com/card.svg", {
  signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const type = response.headers.get("content-type") ?? "";
if (!type.includes("svg") && !type.includes("xml")) {
  throw new Error(`Expected SVG or XML content, got ${type}`);
}
const data = new Uint8Array(await response.arrayBuffer());
await writeFile("card.svg", data);
console.log(`Saved ${data.byteLength} bytes`);

Replace the example host with your own endpoint and check its response contract. A successful HTTP status does not guarantee the body is an SVG; services can return an HTML error page, a challenge page, or an image with an unexpected content type.

5. Make the result more portable

  1. Set a fixed viewport. Define outer SVG dimensions and a viewBox, then set the foreignObject rectangle. Test wrapping at the intended size.
  2. Inline critical styles. External stylesheets and fonts may not be available to a standalone file or a restricted viewer. Prefer self-contained styles and fallback fonts when portability matters.
  3. Control images and resources. Remote images can fail, require authentication, or change over time. If they must appear, determine whether the consumer can fetch them and whether your conversion/export pipeline embeds them.
  4. Avoid dependence on JavaScript. A saved SVG should not assume scripts will execute. In secure animated mode, when an SVG is referenced by an HTML img or SVG image, the W3C conformance criteria require scripts, interactivity, and external file references in the foreignObject content to be disabled. [W3C SVG 2: Conformance Criteria]
  5. Validate in the actual consumer. Open the output as a standalone SVG, inline it in HTML if that is the deployment mode, and test each viewer that matters. A capability in one browser is not proof of universal fidelity.

6. When a screenshot is the better output

If you want a visual record of a page, converting the page into SVG is often extra complexity. A screenshot preserves the rendered appearance as pixels and avoids the question of whether a viewer supports embedded HTML. A capture service can also render the page in a browser without requiring you to install and operate a browser yourself.

A screenshot captures rendered pixels; foreignObject embeds content in an SVG document.
A screenshot captures rendered pixels; foreignObject embeds content in an SVG document.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It returns PNG, JPEG, WebP, or PDF from one GET request. This captures a raster image or PDF; it does not claim to turn HTML into native SVG paths. See the ScreenshotNeo API documentation for its request options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', bytes));

Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. The MCP server gives AI agents tools named take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan. Sign up for 1,000 free screenshots a month, with no card.

7. Troubleshooting

Symptom Likely cause What to try
The embedded content is blank Missing XHTML namespace, fragment outside foreignObject, or unsupported rendering context For standalone SVG, declare the XHTML namespace; put HTML under foreignObject; test as a standalone file and in the actual target viewer.
Text wraps or clips unexpectedly The foreign viewport is too small or has different dimensions than expected Adjust the foreignObject width and height and the outer viewBox; check padding, box sizing, and overflow.
External font or image is missing The viewer cannot fetch the resource, the URL is inaccessible, or the resource is not embedded Use an available fallback font; verify the resource URL and access policy; inline or embed required resources only if your pipeline and licensing permit.
It works inline but not as a file Document namespace context differs Serialize explicit namespace declarations, especially XHTML for a standalone document.
Scripts or links do not work when used as an image Image-reference security rules disable scripts, interactivity, and external references Do not depend on active behavior in an SVG loaded through img; use inline SVG in a controlled page if interaction is essential, and apply a strict security policy.
An API response is not an SVG The endpoint returned an error or challenge body, or its output is raster Check status, content type, response body, and documented output semantics before saving or parsing.
SVG opens but is not editable as vector artwork The file embeds HTML instead of drawing primitives Choose a converter that explicitly emits native SVG paths and text, then verify its supported input and output structure.

8. Performance, reliability, and cost

A static foreignObject adds document markup and asks the consumer to lay out embedded HTML. Runtime and memory depend on the fragment, styles, resources, and rendering engine; the standards sources provide no benchmark that would support a general speed claim. Keep the fragment small, avoid unnecessary external resources, and measure in the actual target context.

For a repeatable result, fix dimensions, content, styles, and resource versions. Browser rendering can vary with font availability and engine behavior, so retain representative output files and check them after changing the capture environment. For remote API use, set connect/read timeouts, check HTTP status and response type, and avoid retrying non-idempotent operations unless the endpoint documents safe retry behavior. Cache stable output when the content is unchanged.

Cost cannot be compared responsibly without a specific converter’s current documented pricing and billing unit. Before adopting an API, check whether charges are per request, render duration, output size, or another unit; whether failed renders are charged; and whether batch use changes the rate. Run a small evaluation against your own pages and viewers, measuring fidelity and failure rate rather than assuming the file extension guarantees conversion quality.

9. How to evaluate an HTML-to-SVG API

  1. Ask what “SVG” means. Does the result contain foreignObject, or does it contain native path, text, and shape elements?
  2. List your input features. Include CSS layout, fonts, images, generated content, responsive behavior, and any dynamic state that matters.
  3. Check the rendering context. Test standalone SVG, inline SVG, and any downstream viewer or image pipeline you will use.
  4. Inspect output and errors. Confirm MIME type, dimensions, escaping, resource handling, and what happens on malformed input or timeouts.
  5. Confirm operational terms. Verify API limits, batching, privacy and retention, geographic handling, failure billing, and current pricing directly with the vendor.

The available standards explain how embedding behaves; they do not establish any named converter’s feature set, fidelity, or price. Treat those as claims to verify with vendor documentation and a reproducible sample using your own input.

FAQ

Does an SVG containing HTML count as true vector output?

The outer document is SVG, but the embedded HTML is not thereby converted into native SVG geometry. A viewer must support rendering the foreign content.

Can I use a foreignObject SVG in an img tag?

Rendering behavior depends on the SVG reference mode and consumer. In secure animated mode, the W3C rules disable scripts, interactivity, and external file references for content referenced through HTML img or SVG image. Test the exact target context.

Will every browser and vector editor show the same result?

The cited standards and MDN documentation do not guarantee identical behavior across all consumers. Validate your own target browsers and tools.

Can ScreenshotNeo produce SVG?

ScreenshotNeo’s stated outputs are PNG, JPEG, WebP, and PDF. Use its capture API when a screenshot or PDF is the deliverable; use an SVG embedding or a verified native-vector converter when SVG is required.