Skip to Content

Usage with TypeScript

Vue Flow is written in TypeScript because we value the additional safety barrier it provides. We export all the types you need for correctly typing data structures and functions you pass to the Vue Flow component. We also provide a way to extend the types of nodes and edges.

Basic Usage

Let’s start with the essential types needed for a basic implementation. While TypeScript can infer some types automatically, we’ll define them explicitly for clarity.

<script setup lang="ts"> import type { DefaultEdgeOptions, Edge, FitViewOptions, Node } from '@xyflow/vue'; import { shallowRef } from 'vue'; import { Background, Controls, VueFlow } from '@xyflow/vue'; const nodes = shallowRef<Node[]>([ { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 5, y: 5 }, }, { id: '2', type: 'default', data: { label: 'Node 2' }, position: { x: 5, y: 100 }, }, ]); const edges = shallowRef<Edge[]>([{ id: 'e1-2', source: '1', target: '2' }]); const fitViewOptions: FitViewOptions = { padding: 0.2, }; const defaultEdgeOptions: DefaultEdgeOptions = { animated: true, }; </script> <template> <VueFlow v-model:nodes="nodes" v-model:edges="edges" fit-view :fit-view-options="fitViewOptions" :default-edge-options="defaultEdgeOptions" > <Controls /> <Background variant="dots" /> </VueFlow> </template>

Custom Nodes

When working with custom nodes, you can extend the base Node type to include your custom data and pass it to NodeProps as a generic. It’s a good idea to keep these types in a shared types.ts so you can reuse them:

types.ts
import type { Node } from '@xyflow/vue'; export type NumberNode = Node<{ number: number }, 'number'>;

When defining node data, you must use a type alias (interfaces won’t satisfy the Record<string, unknown> constraint on node data).

NumberNode.vue
<script setup lang="ts"> import type { NodeProps } from '@xyflow/vue'; import { Handle, Position } from '@xyflow/vue'; import type { NumberNode } from './types'; const { data } = defineProps<NodeProps<NumberNode>>(); </script> <template> <div class="custom"> <div>A special number: {{ data.number }}</div> <Handle type="source" :position="Position.Right" /> </div> </template>

For a single custom node that renders different content based on the node type, use a union type and narrow on type:

CustomNode.vue
<script setup lang="ts"> import type { Node, NodeProps } from '@xyflow/vue'; import { Handle, Position } from '@xyflow/vue'; type NumberNode = Node<{ number: number }, 'number'>; type TextNode = Node<{ text: string }, 'text'>; type AppNode = NumberNode | TextNode; const props = defineProps<NodeProps<AppNode>>(); </script> <template> <div class="custom"> <div v-if="props.type === 'number'">A special number: {{ props.data.number }}</div> <div v-else>A special text: {{ props.data.text }}</div> <Handle type="source" :position="Position.Right" /> </div> </template>

Vue templates do not narrow props.data from a props.type check; for strict type safety, narrow in <script setup> (for example with the type guards below).

Custom Edges

Similar to custom nodes, you can extend the base Edge type for custom edges:

CustomEdge.vue
<script setup lang="ts"> import type { Edge, EdgeProps } from '@xyflow/vue'; import { computed } from 'vue'; import { BaseEdge, getStraightPath } from '@xyflow/vue'; type CustomEdge = Edge<{ value: number }, 'custom'>; const props = defineProps<EdgeProps<CustomEdge>>(); const path = computed(() => getStraightPath({ sourceX: props.sourceX, sourceY: props.sourceY, targetX: props.targetX, targetY: props.targetY, }), ); </script> <template> <BaseEdge :id="id" :path="path[0]" /> </template>

Advanced Usage

In complex applications, you’ll likely have multiple custom nodes and edges with different data structures. When using built-in functions and composables, you’ll need to properly narrow down  the types to prevent runtime errors.

Node and Edge Type Unions

Many functions, callbacks, and composables (including the <VueFlow /> component) accept NodeType or EdgeType generics. These are unions of all your custom node and edge types. As long as you’ve properly typed your data objects, you can reuse their exported types, typically from a shared types.ts:

If you’re using any built-in nodes ('input', 'output', 'default', 'group'), include the BuiltInNode type from @xyflow/vue in your node union. Built-in edges are already covered by the base Edge type.

types.ts
import type { BuiltInNode, Edge, Node } from '@xyflow/vue'; export type NumberNode = Node<{ number: number }, 'number'>; export type TextNode = Node<{ text: string }, 'text'>; export type CustomEdge = Edge<{ value: number }, 'custom'>; export type AppNode = BuiltInNode | NumberNode | TextNode; export type AppEdge = Edge | CustomEdge;

You then register the components through the #node-<type> / #edge-<type> slots and bind your typed arrays:

App.vue
<script setup lang="ts"> import type { AppEdge, AppNode } from './types'; import { shallowRef } from 'vue'; import { VueFlow } from '@xyflow/vue'; import CustomEdge from './CustomEdge.vue'; import NumberNode from './NumberNode.vue'; import TextNode from './TextNode.vue'; const nodes = shallowRef<AppNode[]>([]); const edges = shallowRef<AppEdge[]>([]); </script> <template> <VueFlow v-model:nodes="nodes" v-model:edges="edges" fit-view> <template #node-number="props"> <NumberNode v-bind="props" /> </template> <template #node-text="props"> <TextNode v-bind="props" /> </template> <template #edge-custom="props"> <CustomEdge v-bind="props" /> </template> </VueFlow> </template>

Composables

You can use these type unions to properly type the return values of composables:

<script setup lang="ts"> import type { AppEdge, AppNode } from './types'; import { watchEffect } from 'vue'; import { useNodeConnections, useNodesData, useVueFlow } from '@xyflow/vue'; // nodes and edges are now correctly typed const { getNodes, getEdges } = useVueFlow<AppNode, AppEdge>(); const connections = useNodeConnections({ handleType: 'target' }); const nodesData = useNodesData<AppNode>(() => connections.value.map(c => c.source)); watchEffect(() => { nodesData.value.forEach((node) => { if (node.type === 'number') { // type-safe access to the number property console.log(node.data.number); } }); }); </script>

Type Guards

TypeScript provides several ways to implement type guards . One common approach is to create type guard functions like isNumberNode to filter specific nodes from a list:

import type { AppNode, NumberNode } from './types'; function isNumberNode(node: AppNode): node is NumberNode { return node.type === 'number'; } // numberNodes is now correctly typed as NumberNode[] const numberNodes = computed(() => nodes.value.filter(isNumberNode));

Vue Flow also ships the isNode and isEdge type guards for narrowing an element to a node or an edge.

Last updated on