Use the mounted DiagramInstance to export the current view as PNG, SVG, or PDF, or to carry the scene graph inside an SVG or PNG for an editable round-trip.
When to use each export
export('png', { scale: 2 })returns a PNGdata:URL for raster images.export('svg')returns SVG source. Labels remain SVG text, and vector decorations remain in the document.export('pdf')returns a PDFdata:URL. The PDF uses selectable text and vector paths.- Pass
embedModel: trueto PNG or SVG exports when the recipient must be able to reopen the diagram as data rather than receive a flat picture. - Use
exportPdf()when you need synchronous PDF bytes, orexportSvgString()when you need synchronous, DOM-free SVG and its warnings.
Export after the diagram has initialized and painted. A user action such as a Download button is a suitable point; call renderNow() first when your code changes the model and must export that change immediately.
Export a mounted diagram
The following plain JavaScript example mounts real nodes and links, gives the host a height, and downloads all three visual formats. Each button changes the download, while the mounted diagram remains visible.
jsimport { render } from '@grafloria/element';
let host = document.getElementById('diagram');
if (!(host instanceof HTMLElement)) {
host = document.createElement('div');
host.id = 'diagram';
document.body.append(host);
}
host.style.height = '400px';
const spec = {
nodes: [
{ id: 'requirements', label: 'Requirements', position: { x: 40, y: 80 }, size: { width: 180, height: 70 } },
{ id: 'design', label: 'Design', position: { x: 300, y: 80 }, size: { width: 180, height: 70 } },
{ id: 'ship', label: 'Ship', position: { x: 560, y: 80 }, size: { width: 180, height: 70 } },
],
edges: [{ id: 'requirements-design', source: 'requirements', target: 'design' }, { id: 'design-ship', source: 'design', target: 'ship' }],
};
const instance = render(spec, host);
instance.renderNow();
function download(href, filename) {
const link = document.createElement('a');
link.href = href;
link.download = filename;
link.click();
}
function getButton(id, label) {
const existing = document.querySelector(`#${id}`);
if (existing instanceof HTMLButtonElement) return existing;
const button = document.createElement('button');
button.id = id;
button.textContent = label;
document.body.append(button);
return button;
}
const pngButton = getButton('png', 'Download PNG');
const svgButton = getButton('svg', 'Download SVG');
const pdfButton = getButton('pdf', 'Download PDF');
pngButton.addEventListener('click', async () => download(await instance.export('png', { scale: 2 }), 'diagram.png'));
svgButton.addEventListener('click', async () => {
const svg = await instance.export('svg');
download(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`, 'diagram.svg');
});
pdfButton.addEventListener('click', async () => download(await instance.export('pdf'), 'diagram.pdf'));
html<button id="png">Download PNG</button>
<button id="svg">Download SVG</button>
<button id="pdf">Download PDF</button>
<div id="diagram" style="height: 400px"></div>
<script type="module" src="./diagram.js"></script>
The PNG is a raster image at twice the export scale. The SVG contains the node labels and link artwork as vector content. The PDF opens as a vector document whose labels can be selected.
The same instance in each binding shown here
The bindings expose the same instance after mount. Keep that instance in a ref, signal, or field; do not recreate it in a render. These examples export the mounted diagram as a PNG and leave it on screen.
tsximport { useRef } from 'react';
import { GrafloriaFlow } from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';
const nodes = [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }];
const edges = [{ id: 'ab', source: 'a', target: 'b' }];
export default function ExportExample() {
const instance = useRef<DiagramInstance | null>(null);
const exportPng = async () => { const dataUrl = await instance.current!.export('png', { scale: 2 }); const link = document.createElement('a'); link.href = dataUrl; link.download = 'diagram.png'; link.click(); };
return <><button onClick={exportPng}>Download PNG</button><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} style={{ display: 'block', height: 400 }} onInit={(value) => { instance.current = value; }} /></>;
}
vue<script setup lang="ts"> import { ref } from 'vue'; import { GrafloriaFlow } from '@grafloria/vue'; import type { DiagramInstance } from '@grafloria/vue'; const nodes = [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }]; const edges = [{ id: 'ab', source: 'a', target: 'b' }]; const instance = ref<DiagramInstance | null>(null); async function exportPng() { const dataUrl = await instance.value!.export('png', { scale: 2 }); const link = document.createElement('a'); link.href = dataUrl; link.download = 'diagram.png'; link.click(); } </script> <template><button @click="exportPng">Download PNG</button><GrafloriaFlow :default-nodes="nodes" :default-edges="edges" style="display:block;height:400px" @init="instance = $event" /></template>
tsximport { component$, $, noSerialize, useSignal, type NoSerialize } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/qwik';
const nodes = [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }];
const edges = [{ id: 'ab', source: 'a', target: 'b' }];
export default component$(() => {
const instance = useSignal<NoSerialize<DiagramInstance>>();
const exportPng = $(async () => {
const dataUrl = await instance.value!.export('png', { scale: 2 });
const link = document.createElement('a');
link.href = dataUrl;
link.download = 'diagram.png';
link.click();
});
return <><button onClick$={exportPng}>Download PNG</button><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} style={{ display: 'block', height: '400px' }} onInit$={$((value: DiagramInstance) => { instance.value = noSerialize(value); })} /></>;
});
tsimport { Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
@Component({ standalone: true, imports: [DiagramCanvasComponent], template: `<button (click)="exportPng()">Download PNG</button><grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:400px" />` })
export class ExportExampleComponent {
canvas = viewChild.required(DiagramCanvasComponent);
nodes = [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }];
edges = [{ id: 'ab', source: 'a', target: 'b' }];
async exportPng() { const dataUrl = await this.canvas().exportDiagram('png', { scale: 2 }); const link = document.createElement('a'); link.href = dataUrl; link.download = 'diagram.png'; link.click(); }
}
jsimport { render } from '@grafloria/element';
const host = document.querySelector('#diagram');
if (!(host instanceof HTMLElement)) throw new Error('Missing #diagram');
host.style.height = '400px';
const instance = render({ nodes: [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }], edges: [{ id: 'ab', source: 'a', target: 'b' }] }, host);
instance.renderNow();
(async () => {
const png = await instance.export('png', { scale: 2 });
console.log(png);
})();
Preserve an editable artifact
Set embedModel: true on an asynchronous SVG or PNG export. The file then carries the document model alongside its visual content. Use isEditableArtifact to test an artifact and extractModel to read its embedded document. A plain image returns false and null.
jsimport { render } from '@grafloria/element';
import { extractModel, isEditableArtifact } from '@grafloria/renderer';
const host = document.querySelector('#source');
if (!(host instanceof HTMLElement)) throw new Error('Missing #source');
host.style.height = '400px';
const instance = render({ nodes: [{ id: 'a', label: 'Author', position: { x: 40, y: 40 } }, { id: 'b', label: 'Review', position: { x: 260, y: 40 } }], edges: [{ id: 'ab', source: 'a', target: 'b' }] }, host);
instance.renderNow();
(async () => {
const svg = await instance.export('svg', { embedModel: true, embedModelCreatedAt: '2020-01-01T00:00:00Z' });
if (!isEditableArtifact(svg)) throw new Error('The model was not embedded.');
const envelope = extractModel(svg);
if (envelope === null) throw new Error('No model found.');
console.log(envelope);
})();
The envelope is the scene graph recovered from the SVG. Mount the recovered document through your application’s binding to get a live diagram again. A fixed embedModelCreatedAt makes repeated exports deterministic when you compare artifact bytes.
Options that matter
| Option | Type | Default | What it does |
|---|---|---|---|
scale | number | — | Sets the raster scale for PNG and JPEG exports. |
embedModel | boolean | false | Embeds the model in PNG or SVG so the artifact can be reopened. |
embedModelCreatedAt | string | — | Supplies the embedded envelope timestamp. |
assetFetcher | ExportOptions.assetFetcher | — | Supplies an allowlisted fetcher for external images blocked by browser CORS during asynchronous export. |
Pitfalls
- Export the current view from the mounted instance; do not export an independent model.
- Give the host a resolved height. See editing Mermaid diagrams.
- Wait for initialization and a painted frame before exporting. See editing Mermaid diagrams.
exportSvgString()andexportPdf()are synchronous and report warnings instead of waiting for an asynchronous custom painter. Useawait export('svg')orawait export('pdf')when the export must wait.- Model embedding is opt-in. An export without
embedModel: trueis a flat visual artifact.
See it running
-
Download image demo — PNG and SVG include labels, arrowheads, and shadows.
-
PDF export demo — the document keeps selectable text and vector paths.
-
Editable round-trip demo — the reopened pane contains the recovered model.
Was this page helpful?