# Export diagrams

Use the mounted [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-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 PNG `data:` URL for raster images.
- `export('svg')` returns SVG source. Labels remain SVG text, and vector decorations remain in the document.
- `export('pdf')` returns a PDF `data:` URL. The PDF uses selectable text and vector paths.
- Pass `embedModel: true` to 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, or `exportSvgString()` 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.

```js title="diagram.js"
import { 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 title="index.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.

:::code-group
```tsx title="React"
import { 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 title="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>
```
```tsx title="Qwik"
import { 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); })} /></>;
});
```
```ts title="Angular"
import { 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(); }
}
```
```js title="JavaScript"
import { 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`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-export-functions) to test an artifact and [`extractModel`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-export-functions) to read its embedded document. A plain image returns `false` and `null`.

```js
import { 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](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams).
- Wait for initialization and a painted frame before exporting. See [editing Mermaid diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams).
- `exportSvgString()` and `exportPdf()` are synchronous and report warnings instead of waiting for an asynchronous custom painter. Use `await export('svg')` or `await export('pdf')` when the export must wait.
- Model embedding is opt-in. An export without `embedModel: true` is a flat visual artifact.

## See it running

- [Download image demo](https://grafloria.com/demos/misc/download-image.html) — PNG and SVG include labels, arrowheads, and shadows.

- [PDF export demo](https://grafloria.com/demos/misc/pdf-export.html) — the document keeps selectable text and vector paths.

- [Editable round-trip demo](https://grafloria.com/demos/misc/editable-round-trip.html) — the reopened pane contains the recovered model.

## Related

- [How Grafloria works](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works)
- [Edit Mermaid diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams)
- [Customize nodes](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/customize-nodes)
