# Save and restore diagrams

Save the mounted model as a document, then mount that document again without rebuilding its graph by hand. The shared handle is [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance); [`DiagramSerializer`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-serialization#diagramserializer) writes its model, [`fromDocument`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#fromdocument) prepares a saved document, and [`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#render) mounts it.

## Save and restore with JavaScript

Give the host a resolved height. This example renders a connected graph, serializes its live model, deserializes the saved document, and reconciles its nodes back into the same mounted instance rather than creating a second host.

```js
import { DiagramSerializer, fromDocument, render } from '@grafloria/element';

const sourceHost = document.getElementById('source');
if (!sourceHost) throw new Error('diagram host is missing');
sourceHost.style.height = '240px';
const source = render({
  nodes: [
    { id: 'start', position: { x: 80, y: 100 }, size: { width: 140, height: 56 }, label: 'Start' },
    { id: 'finish', position: { x: 340, y: 100 }, size: { width: 140, height: 56 }, label: 'Finish' },
  ],
  edges: [{ id: 'path', source: 'start', target: 'finish', label: 'continues' }],
}, sourceHost);
const saved = JSON.stringify(new DiagramSerializer().serialize(source.getModel()));
const loaded = new DiagramSerializer().deserialize(JSON.parse(saved));
source.setNodes(loaded.getNodes().map((node) => ({
  id: node.id,
  position: { x: node.position.x, y: node.position.y },
  size: { width: node.size.width, height: node.size.height },
  label: node.getLabel(),
})));
source.renderNow();
```

After `renderNow()`, the mounted source canvas contains the two saved nodes and their labels again.

```html
<div id="source" style="height: 240px"></div>
```

`serialize()` returns a plain object. `JSON.stringify()` makes it suitable for storage. `fromDocument()` accepts that JSON string, the flat serializer object, or the portable envelope. `render()` returns the new live instance.

## Framework bindings

Every binding exposes the same live instance through its initialization callback. Serialize `getModel()` after the component mounts. The Angular binding also provides `snapshot()` and `loadSnapshot()` for saving and reconciling into the existing canvas.

:::code-group
```ts title="Angular"
import { AfterViewInit, Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { SerializedDiagram } from '@grafloria/engine';

@Component({ standalone: true, imports: [DiagramCanvasComponent], template: '<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:400px" />' })
export class SaveRestoreComponent implements AfterViewInit {
  canvas = viewChild.required(DiagramCanvasComponent);
  saved: SerializedDiagram | null = null;
  nodes = [{ id: 'a', position: { x: 80, y: 100 }, label: 'A' }, { id: 'b', position: { x: 320, y: 100 }, label: 'B' }];
  edges = [{ id: 'ab', source: 'a', target: 'b' }];
  ngAfterViewInit(): void { this.saved = this.canvas().snapshot(); }
  restore(): void { if (this.saved) this.canvas().loadSnapshot(this.saved); }
}
```

```tsx title="React"
import { useRef } from 'react';
import { GrafloriaFlow } from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';
import { DiagramSerializer } from '@grafloria/engine';

const nodes = [{ id: 'a', position: { x: 80, y: 100 }, label: 'A' }, { id: 'b', position: { x: 300, y: 100 }, label: 'B' }];
const edges = [{ id: 'ab', source: 'a', target: 'b' }];
import type { ReactElement } from 'react';
export default function SaveRestore(): ReactElement {
  const api = useRef<DiagramInstance | null>(null);
  const save = (): void => { if (api.current) localStorage.setItem('diagram', JSON.stringify(new DiagramSerializer().serialize(api.current.getModel()))); };
  return <div style={{ height: 400 }}><button onClick={save}>Save</button><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} onInit={(instance) => { api.current = instance; }} /></div>;
}
```

```vue title="Vue"
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow } from '@grafloria/vue';
import type { DiagramInstance } from '@grafloria/vue';
import { DiagramSerializer } from '@grafloria/engine';
const api = ref<DiagramInstance | null>(null);
const nodes = [{ id: 'a', position: { x: 80, y: 100 }, label: 'A' }, { id: 'b', position: { x: 300, y: 100 }, label: 'B' }];
const edges = [{ id: 'ab', source: 'a', target: 'b' }];
function save(): void { if (api.value) localStorage.setItem('diagram', JSON.stringify(new DiagramSerializer().serialize(api.value.getModel()))); }
</script>
<template><div style="height:400px"><button @click="save">Save</button><GrafloriaFlow :default-nodes="nodes" :default-edges="edges" @init="(instance: DiagramInstance) => api = instance" /></div></template>
```

```tsx title="Qwik"
import { component$, $, noSerialize, useSignal, type NoSerialize } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/qwik';
import { DiagramSerializer } from '@grafloria/engine';
const nodes = [{ id: 'a', position: { x: 80, y: 100 }, label: 'A' }, { id: 'b', position: { x: 300, y: 100 }, label: 'B' }];
const edges = [{ id: 'ab', source: 'a', target: 'b' }];
export default component$(() => { const api = useSignal<NoSerialize<DiagramInstance>>(); const save = $(() => { if (api.value) localStorage.setItem('diagram', JSON.stringify(new DiagramSerializer().serialize(api.value.getModel()))); }); return <div style={{ height: '400px' }}><button onClick$={save}>Save</button><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} onInit$={$((instance: DiagramInstance) => { api.value = noSerialize(instance); })} /></div>; });
```
:::

The JavaScript, Qwik, React, and Vue examples save the model owned by the mounted component. Angular's `snapshot()` captures the document and `loadSnapshot()` reconciles nodes and edges into the same live model, keeping its renderer and listeners attached.

## Preserve metadata

The serialized model carries node and link metadata, group membership, geometry, and link waypoints. For new persistence, use the portable envelope; it carries generator identity, creation time, and an integrity checksum. `deserialize()` accepts both the envelope and the flat form.

```ts
import { DiagramSerializer } from '@grafloria/engine';
import { render } from '@grafloria/element';

const host = document.getElementById('diagram');
if (!host) throw new Error('diagram host is missing');
host.style.height = '240px';
const instance = render({
  nodes: [{ id: 'saved', position: { x: 80, y: 100 }, label: 'Saved' }],
  edges: [],
}, host);
const serializer = new DiagramSerializer();
const saved = serializer.serialize(instance.getModel());
const model = serializer.deserialize(saved);
```

Use `fromDocument()` when the result must become a renderable spec. Use `deserialize()` when you need to inspect or transform the model before mounting.

## Pitfalls

- A custom renderer function is not data and does not serialize. Register or pass the renderer again when you mount the restored document.
- Do not put user-supplied values into a renderer's `innerHTML`; use `textContent` instead, so restoring metadata cannot create an XSS path.
- Give every diagram host a real height, or the restored graph has space in the model but no visible canvas.

## Live demo

Try the [save and restore demo](https://grafloria.com/demos/interaction/save-and-restore.html) and [view its source](https://github.com/grafloria/grafloria/blob/ef2bcc55237d4d1f1643b6fea68bd996aa37a9e9/demos/interaction/save-and-restore.html).

## Related

- [How Grafloria works](https://atloria.dev/p/grafloria-h7YM7amryF/developer/how-grafloria-works)
- [Round-trip Mermaid text](https://atloria.dev/p/grafloria-h7YM7amryF/developer/round-trip-mermaid-text)
- [Add collaboration](https://atloria.dev/p/grafloria-h7YM7amryF/developer/add-collaboration)
- [Style a diagram](https://atloria.dev/p/grafloria-h7YM7amryF/developer/style-a-diagram)
