SVG vs PNG for AI-Generated Reports: Which Format, When, and How to Embed It

HTMLvault Team·October 25, 2026·11 min read

Explore with AI

Open this article in an AI assistant, with a prompt that asks it for a summary and the takeaways.

Liz Lemmon's Q3 board report has one chart in it, and the chart is a PNG. Finance has restated Q3 four times since Monday, so she is about to ask an AI to redraw an entire image to move one bar eleven pixels. Her prompt history has started to read like a negotiation.

SVG vs PNG: the short answer

SVG vs PNG comes down to how the image is stored. An SVG (Scalable Vector Graphics) image is a text description of shapes, such as rectangles, paths and text. The browser redraws them at whatever size the screen needs, so the image stays sharp on a phone and on a projector. A PNG is a fixed grid of pixels. Enlarged past its native size, it blurs or turns to steps.

So the content picks the format. Charts, icons, logos and diagrams begin as shapes and belong in SVG. Screenshots begin as pixels and belong in PNG. Photos belong in JPEG, which compresses them far smaller.

For reports an AI writes as HTML, the biggest difference is that inline SVG is text in the page, so the model that drew a chart can later change one number in it. A PNG chart can only be redrawn.

A diagonal edge drawn as SVG and as an enlarged PNG The same diagonal edge at 4x zoom INLINE SVG PNG, ENLARGED Redrawn from shapes: still sharp Enlarged pixels: visible steps
In a report an AI writes as HTML, an SVG chart keeps clean edges at any zoom or screen size, while a PNG chart stays locked to the pixel grid it was exported at.

The decision table: inline SVG, hosted image or base64

In a single HTML file an image can arrive three ways. It can be drawn inline as SVG, referenced by URL from another host, or embedded as base64, a text encoding of the file's bytes written into the page itself.

Decision table comparing inline SVG, hosted images and base64 images Three ways to put an image in one HTML file INLINE SVG HOSTED PNG/JPEG BASE64 PNG/JPEG Sharpness Sharp at any size Fixed pixel grid Fixed pixel grid Size in the page Small for charts and icons Not in the page; fetched by URL File size plus about a third Agent edits later Yes: patch the numbers as text No: replace the image file No: redraw and re-encode Accessible name title and desc alt text title or aria-label Pasted in email Often not shown Widely supported Often blocked On HTMLvault Kept, with tags normalized Kept by URL (http or https) Dropped from <img>, kept in CSS or SVG
Inline SVG is the only format an agent can edit in place after publishing, which is why charts belong in SVG and pixels belong to screenshots and photos.

Three rows need a sentence each.

  • Agent edits later. An SVG bar's height and label are attribute values and text that an agent can change in place. A PNG holds only pixels.
  • Pasted in email. Many email clients won't show inline SVG, and many block base64 images. That only matters when the HTML itself is the email body. Send a link instead and the page opens in a browser, where SVG renders normally. Heavy HTML in the email body hurts deliverability anyway.
  • On HTMLvault. Publishing keeps inline SVG and image URLs, but it removes a data: URL from <img src>. A base64 image has to go in CSS or in an SVG <image>, as shown below.

Rules for images in a single HTML file

A person or a model can check a finished page against each rule. Where HTMLvault's publishing step is the reason for a rule, the rule says so.

  1. Draw charts, icons, logos and diagrams as inline <svg>, never as image files. They stay sharp at any size and stay editable as text.
  2. Give every inline SVG role="img" and an aria-labelledby that points at a <title> placed as its first child. On a chart, add a <desc> that lists the values. An SVG has no alt attribute, so the title is its accessible name.
  3. Write an SVG <title> as plain text with no tags and no ampersands, and keep <script> and <style> outside the <svg>. Inside SVG, those elements are parsed as markup, so HTMLvault can refuse one that holds a tag, a character reference or a CDATA section. An SVG <style> of plain CSS is fine.
  4. Use PNG for screenshots and JPEG for photos. Both began as pixels, and tracing them into SVG adds weight without adding sharpness.
  5. Give every <img> alt text that says what it shows. Use alt="" only for decoration. Publishing removes empty attributes but keeps an empty alt.
  6. Reference images by a stable https URL, or embed base64 in a CSS url() or an SVG <image xlink:href>. Never use <img src="data:…">. HTMLvault removes data: URLs from src, so that image disappears when you publish.
  7. Never point at a local path (C:\Users\…, file://) or at a host that may vanish, such as an expiring signed URL or a tool's temporary preview. The reader's browser can't reach your disk, and when a borrowed host goes away, the image goes with it.
  8. Close every element explicitly, including SVG shapes (<rect …></rect>) and every <p>, <li> and <td>. HTMLvault refuses malformed HTML instead of repairing it.

A screenshot referenced by URL is the lightest option:

<img src="https://assets.example.com/q3/pipeline.png"
     alt="CRM pipeline: 14 open deals, 3 in legal review"
     width="1200" height="720" />

Here is the same screenshot embedded, for a report that must not depend on another host:

<svg viewBox="0 0 1200 720" role="img" aria-labelledby="shot-title">
  <title id="shot-title">CRM pipeline: 14 open deals, 3 in legal review</title>
  <image width="1200" height="720" xlink:href="data:image/png;base64,iVBORw0KGgo…"></image>
</svg>

A CSS background-image: url("data:…") also survives publishing. Give that element role="img" and an aria-label so it still has a name.

Version two of Liz's report pulled its chart from a folder on her laptop. It rendered perfectly in every review, because every review happened on her laptop. The board saw a broken-image icon and the alt text "chart," which one director called admirably concise.

How to convert an image to base64, and when a page gets too heavy

Run the conversion with a command, not the model. A model that types out base64 spends output tokens on every character and has no way to check it got them right.

# macOS
base64 -i screenshot.png | tr -d '\n' > screenshot.b64

# Linux
base64 -w 0 screenshot.png > screenshot.b64

# Windows PowerShell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\reports\screenshot.png")) | Set-Content -NoNewline screenshot.b64

Put the media type in front of the result (data:image/png;base64, or data:image/jpeg;base64,) and place it in the SVG <image> or CSS url() shown above.

Base64 writes every 3 bytes as 4 characters, so an embedded image is about a third larger than its file. Before encoding, resize the image to the size it will display at and save photos as JPEG.

One stored HTMLvault document can be at most 5 MB. Once encoded, that holds roughly 3.75 MB of image files, minus the rest of the page. As a rule of thumb, once embedded images pass a megabyte or so, host them and reference them by https URL. The page loads faster, and the AI tool doesn't have to write out megabytes of text. You can still build a large page in pieces with append mode: start a draft (create_draft_link or POST /api/v1/links/draft), send chunks of up to 200 KB with append_html, and finalize on the last call. Nothing is public and no checks run until the draft is finalized. The 5 MB ceiling still applies.

File size on disk versus base64 size for three example images Base64 adds about a third to an embedded image Image file on disk Base64 inside the page 5 MB document limit Cropped screenshot Full-page screenshot Six product photos 0.3 MB 0.4 MB 1.5 MB 2.0 MB 3.9 MB 5.2 MB 0 MB 1 MB 2 MB 3 MB 4 MB 5 MB 6 MB
Base64 inflates every embedded file by about a third, so 3.9 MB of photos becomes 5.2 MB of page and crosses HTMLvault's 5 MB document limit. Host images that size by https URL.

Inline SVG is text: patch a chart instead of regenerating it

An inline SVG chart keeps its numbers as attribute values and text, so when a figure changes after the report has gone out, an agent edits just those strings and leaves the rest of the page alone. A PNG chart has to be regenerated and swapped in.

On HTMLvault, a patch replaces an exact substring of the stored HTML, and the URL stays the same. You send it with patch_link over MCP, or with PATCH /api/v1/links/{slug} and a patches field. Matching is plain text, not regex. One call carries up to 200 patches and applies all of them or none. Unless a patch sets replace_all, its find string must occur exactly once in the page.

Make the chart patchable

  • Give each bar, point and value label an id so that a find string including it is unique.
  • Write markup close to its stored form: double-quoted attributes, explicit closing tags, lowercase HTML names and no comments, since publishing drops comments.
  • Record the chart's scale in a data- attribute so an agent can work out the new geometry from a new value.
  • Copy each find from the stored HTML, which get_link returns. Before publishing, scan_html with normalize: "full" shows you that stored form.

Worked example: Q3 moves from $1.1M to $1.3M

This excerpt of the stored chart uses a scale of 100 units per $1M, measured up from a baseline at y=160:

<desc id="rev-desc">Q1 $0.8M, Q2 $1.0M, Q3 $1.1M</desc>
<g id="rev-bars" data-scale="100 per $1M above y=160">
  <rect id="bar-q3" x="200" y="50" width="48" height="110" fill="#5A52CC"></rect>
  <text id="val-q3" x="224" y="42" text-anchor="middle">$1.1M</text>
</g>

The update is one call with three patches:

find:    <rect id="bar-q3" x="200" y="50" width="48" height="110" fill="#5A52CC"></rect>
replace: <rect id="bar-q3" x="200" y="30" width="48" height="130" fill="#5A52CC"></rect>

find:    <text id="val-q3" x="224" y="42" text-anchor="middle">$1.1M</text>
replace: <text id="val-q3" x="224" y="22" text-anchor="middle">$1.3M</text>

find:    Q3 $1.1M
replace: Q3 $1.3M

The new height is 1.3 × 100 = 130, so y is 160 − 130 = 30, and the label sits 8 units above the bar. The label's find string includes the whole element because a bare $1.1M appears twice, once in the label and once in the desc. A find that matches twice fails, and because the call is all or nothing, the page stays exactly as it was instead of ending up half-updated. The edit goes through the same secret, personal-data and URL checks as the first publish.

For whole-page replacement and the web editor, see editing published HTML without changing the URL. The MCP server guide covers connecting Claude, and the REST API accepts the same patches from a script.

The fifth restatement landed at 8:52 for a 9:00 board meeting. Liz had Claude fetch the stored page and send three patches, and the directors opened the link they had saved on Monday to a chart that finally agreed with the spreadsheet. J. Pennyman's only question was whether the Q3 bar could be Innovation Blue, which was a fourth patch.

Prompt you can reuse

Paste this into Claude, ChatGPT, Gemini or Cursor when you start a report. If the HTMLvault MCP server is connected, the last paragraph also covers updates.

Build this report as one self-contained HTML document with its own CSS.

Images
- Draw every chart, icon, logo and diagram as inline <svg>, never as an image file.
- Give each <svg> role="img" and aria-labelledby pointing at a <title> that is its first child. On charts, add a <desc> listing the values.
- Write each <title> as plain text: no tags, no ampersands. Keep <script> and <style> outside the <svg>.
- Screenshots are PNG and photos JPEG, referenced by the https URLs I give you, each with alt text saying what it shows.
- If I supply base64, put it in an SVG <image xlink:href> inside an <svg> with a <title>. Never put a data: URL in <img src>, never write base64 yourself, never use a local file path.

Markup
- Double-quote every attribute and close every element explicitly, including <rect></rect>, <p>, <li> and <td>. No HTML comments.
- Give every bar, value label and number-bearing cell a unique id, write each number as a literal, and record each chart's scale in a data-scale attribute.
- Keep the page under 5 MB. If images would bring it close, stop and ask me to host them.

Updates
When I send new numbers, call get_link, then patch_link with one find-and-replace per changed string. Copy each find from the stored HTML, make it unique on the page, and recompute bar geometry from data-scale.

Use it on your next report. The next time a number moves, the fix is a three-string patch to the link your reader already has, not a regenerated chart.

HTMLvault

Publish your AI creations. See who reads them.

Proposals, dashboards and reports from Claude, ChatGPT or any AI tool become private links on your own domain. Update them after you send, and see who read what, down to the scroll.

Create a link

Related posts