Skip to main content
This example demonstrates how to save the complete state of your Vue Flow (nodes, edges, and viewport) to local storage and restore it later.

Live Demo

View the live demo on vueflow.dev.

Complete Example

<script lang="ts" setup>
import type { Elements } from '@vue-flow/core'
import { VueFlow } from '@vue-flow/core'
import Controls from './Controls.vue'

const initialElements: Elements = [
  { id: '1', label: 'Node 1', position: { x: 100, y: 100 } },
  { id: '2', label: 'Node 2', position: { x: 100, y: 200 } },
  { id: 'e1-2', source: '1', target: '2' },
]

const elements = ref(initialElements)
</script>

<template>
  <VueFlow v-model="elements">
    <Controls />
  </VueFlow>
</template>

Key Concepts

Export Flow State

Use toObject() to export the complete flow state:
const { toObject } = useVueFlow()

function onSave() {
  const flowState = toObject()
  console.log(flowState)
}
The returned object contains:
interface FlowExportObject {
  nodes: Node[]
  edges: Edge[]
  position: [number, number]  // [x, y]
  zoom: number
  viewport: { x: number; y: number; zoom: number }
}

Save to Local Storage

Use VueUse’s useStorage for persistent storage:
import { useStorage } from '@vueuse/core'

const state = useStorage('flow-key', {
  nodes: [] as Node[],
  edges: [] as Edge[],
  viewport: { x: 0, y: 0, zoom: 1 },
} as FlowExportObject)

function onSave() {
  state.value = toObject()
}

Restore Flow State

Restore nodes, edges, and viewport:
const { setNodes, setEdges, setViewport } = useVueFlow()

function onRestore() {
  const flow = state.value

  if (flow) {
    setNodes(flow.nodes)
    setEdges(flow.edges)
    setViewport({ 
      x: flow.viewport.x, 
      y: flow.viewport.y, 
      zoom: flow.viewport.zoom 
    })
  }
}

Storage Options

Persist data across browser sessions:
import { useStorage } from '@vueuse/core'

const state = useStorage('flow-state', defaultState)

Export Formats

JSON Export

function exportAsJSON() {
  const flow = toObject()
  const json = JSON.stringify(flow, null, 2)
  
  // Download as file
  const blob = new Blob([json], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'flow.json'
  link.click()
  URL.revokeObjectURL(url)
}

Import from JSON

function importFromJSON(file: File) {
  const reader = new FileReader()
  
  reader.onload = (e) => {
    const flow = JSON.parse(e.target?.result as string)
    setNodes(flow.nodes)
    setEdges(flow.edges)
    setViewport(flow.viewport)
  }
  
  reader.readAsText(file)
}

Auto-Save

Automatically save changes:
import { watchDebounced } from '@vueuse/core'

const { nodes, edges, viewport } = useVueFlow()

// Auto-save after 1 second of no changes
watchDebounced(
  [nodes, edges, viewport],
  () => {
    state.value = toObject()
    console.log('Auto-saved!')
  },
  { debounce: 1000, deep: true }
)

Version Control

Implement versioning for flow states:
interface FlowVersion {
  version: number
  timestamp: number
  state: FlowExportObject
}

const versions = useStorage<FlowVersion[]>('flow-versions', [])

function saveVersion() {
  versions.value.push({
    version: versions.value.length + 1,
    timestamp: Date.now(),
    state: toObject()
  })
}

function restoreVersion(versionNumber: number) {
  const version = versions.value.find(v => v.version === versionNumber)
  if (version) {
    setNodes(version.state.nodes)
    setEdges(version.state.edges)
    setViewport(version.state.viewport)
  }
}

Undo/Redo

Implement undo/redo functionality:
import { useRefHistory } from '@vueuse/core'

const { nodes, edges } = useVueFlow()

const { history, undo, redo, canUndo, canRedo } = useRefHistory(
  { nodes, edges },
  { deep: true, capacity: 50 }
)

function handleUndo() {
  if (canUndo.value) {
    undo()
  }
}

function handleRedo() {
  if (canRedo.value) {
    redo()
  }
}
<template>
  <div class="controls">
    <button :disabled="!canUndo" @click="handleUndo">Undo</button>
    <button :disabled="!canRedo" @click="handleRedo">Redo</button>
  </div>
</template>

Migration and Compatibility

Handle different flow versions:
interface FlowState {
  version: string
  data: FlowExportObject
}

function migrateFlow(oldFlow: any): FlowExportObject {
  // Migrate from old format to new format
  if (oldFlow.version === '1.0') {
    return {
      nodes: oldFlow.elements.filter(isNode),
      edges: oldFlow.elements.filter(isEdge),
      viewport: oldFlow.viewport || { x: 0, y: 0, zoom: 1 }
    }
  }
  return oldFlow.data
}

function onRestore() {
  const savedFlow = state.value
  const flow = migrateFlow(savedFlow)
  
  setNodes(flow.nodes)
  setEdges(flow.edges)
  setViewport(flow.viewport)
}

Partial State Management

Save only specific parts of the state:
// Save only nodes
function saveNodes() {
  const { nodes } = useVueFlow()
  localStorage.setItem('flow-nodes', JSON.stringify(nodes.value))
}

// Save only viewport
function saveViewport() {
  const { viewport } = useVueFlow()
  localStorage.setItem('flow-viewport', JSON.stringify(viewport.value))
}

// Restore selectively
function restoreNodes() {
  const saved = localStorage.getItem('flow-nodes')
  if (saved) {
    setNodes(JSON.parse(saved))
  }
}

Error Handling

function onSave() {
  try {
    const flow = toObject()
    state.value = flow
    console.log('Flow saved successfully')
  } catch (error) {
    console.error('Failed to save flow:', error)
    // Show error notification to user
  }
}

function onRestore() {
  try {
    const flow = state.value
    if (!flow || !flow.nodes) {
      throw new Error('Invalid flow state')
    }
    
    setNodes(flow.nodes)
    setEdges(flow.edges || [])
    setViewport(flow.viewport || { x: 0, y: 0, zoom: 1 })
    
    console.log('Flow restored successfully')
  } catch (error) {
    console.error('Failed to restore flow:', error)
    // Show error notification and restore defaults
    setNodes([])
    setEdges([])
  }
}

TypeScript Support

import type { FlowExportObject, Node, Edge } from '@vue-flow/core'
import { useStorage } from '@vueuse/core'

const defaultState: FlowExportObject = {
  nodes: [],
  edges: [],
  position: [0, 0],
  zoom: 1,
  viewport: { x: 0, y: 0, zoom: 1 }
}

const state = useStorage<FlowExportObject>('flow-state', defaultState)

Best Practices

  • Always validate restored data before applying it
  • Implement versioning for backwards compatibility
  • Use debouncing for auto-save to avoid performance issues
  • Provide visual feedback when saving/restoring
  • Handle storage quota errors gracefully
  • Consider compression for large flows

Next Steps

Drag and Drop

Add nodes via drag and drop

Interaction

Control interaction settings

useVueFlow

Complete composable reference

State Management

Learn about state management

Build docs developers (and LLMs) love