TypeScript
Beginner · 5 min read · Updated Nov 9, 2026
TypeScript: A Complete Guide to Type-Safe Nodes, Edges, and Workflows
TypeScript makes React Flow applications easier to maintain, safer to develop, and more predictable as workflows become complex. Whether you are building a visual workflow editor, automation builder, diagramming tool, or interactive node-based application, using TypeScript with React Flow helps prevent common data and connection errors.
React Flow is built with TypeScript and provides built-in types for nodes, edges, callbacks, hooks, and other essential features. You can also define custom types for your own nodes and edges, allowing TypeScript to understand exactly what data each component receives.
In this guide, you will learn how to use React Flow with TypeScript, define custom node and edge types, type event handlers, and build a scalable workflow editor.
1. Setting Up React Flow with TypeScript
To get started, install React Flow in your React and TypeScript project:
npm install @xyflow/react
Import the required components and types from @xyflow/react:
import {
ReactFlow,
type Node,
type Edge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
React Flow provides exported TypeScript definitions, so you generally do not need to install a separate type package.
2. Defining Node and Edge Types
Every React Flow node contains essential properties such as an ID, position, and data. An edge connects two nodes using their source and target IDs.
You can use the built-in Node and Edge types to define these structures.
import type { Node, Edge } from '@xyflow/react';
const initialNodes: Node[] = [
{
id: '1',
position: { x: 100, y: 100 },
data: { label: 'Start Node' },
},
{
id: '2',
position: { x: 350, y: 100 },
data: { label: 'End Node' },
},
];
const initialEdges: Edge[] = [
{
id: 'e1-2',
source: '1',
target: '2',
},
];
These general types are useful for simple applications. However, when building advanced workflow editors, it is better to define specific data types for each node and edge.
3. Creating Custom Node Types with TypeScript
Custom nodes allow developers to create specialized UI components for different workflow operations.
For example, a workflow editor might contain:
- Number nodes for numeric values
- Text nodes for user input
- API nodes for external requests
- Condition nodes for decision-making
- Database nodes for storing information
You can define a separate TypeScript type for each custom node.
import type { Node, NodeProps } from '@xyflow/react';
type NumberNode = Node<
{ number: number },
'number'
>;
export default function NumberNodeComponent({
data,
}: NodeProps<NumberNode>) {
return (
<div className="number-node">
<strong>Number Node</strong>
<p>Value: {data.number}</p>
</div>
);
}
Here, the node data must contain a numeric number property. TypeScript understands this structure and provides autocomplete and type checking inside the component.
You can also define multiple custom node types:
type TextNode = Node<
{ text: string },
'text'
>;
type NumberNode = Node<
{ number: number },
'number'
>;
type AppNode = TextNode | NumberNode;
This approach is particularly useful when different node components require different data structures.
4. Creating Custom Edge Types with TypeScript
Edges represent connections between nodes. In advanced applications, custom edges can contain additional information such as labels, conditions, connection values, or status indicators.
You can define a custom edge type using the Edge generic:
import type { Edge, EdgeProps } from '@xyflow/react';
type CustomEdge = Edge<
{ value: number },
'custom'
>;
Then use the type in your custom edge component:
import {
BaseEdge,
getStraightPath,
type EdgeProps,
} from '@xyflow/react';
function CustomEdgeComponent({
id,
sourceX,
sourceY,
targetX,
targetY,
}: EdgeProps<CustomEdge>) {
const [edgePath] = getStraightPath({
sourceX,
sourceY,
targetX,
targetY,
});
return (
<BaseEdge
id={id}
path={edgePath}
/>
);
}
Using EdgeProps<CustomEdge> ensures that your custom edge component receives correctly typed properties.
5. Combining Multiple Node and Edge Types
When an application contains several custom node and edge components, you should create union types that represent all supported node and edge variants.
import type {
BuiltInNode,
BuiltInEdge,
} from '@xyflow/react';
type AppNode =
| BuiltInNode
| NumberNode
| TextNode;
type AppEdge =
| BuiltInEdge
| CustomEdge;
Including BuiltInNode and BuiltInEdge allows your application to support React Flow's built-in node and edge types alongside custom components.
These union types become especially useful when passing nodes and edges to the main React Flow component, hooks, and callback functions.
6. Typing React Flow Event Handlers
React Flow provides several event handlers for managing node changes, edge changes, connections, and dragging.
TypeScript allows you to explicitly define the correct type for each callback.
For example, use OnConnect to type a connection handler:
import {
addEdge,
type OnConnect,
} from '@xyflow/react';
const onConnect: OnConnect = (connection) => {
setEdges((edges) =>
addEdge(connection, edges)
);
};
For applications with custom node types, you can provide your union type to callbacks:
import type { OnNodeDrag } from '@xyflow/react';
const onNodeDrag: OnNodeDrag<AppNode> = (
event,
node
) => {
if (node.type === 'number') {
console.log(node.data.number);
}
};
TypeScript can narrow the node type based on its type property. This means you can safely access the properties associated with a specific node.
7. Using TypeScript with React Flow Hooks
React Flow provides hooks for reading and managing nodes, edges, connections, and viewport information.
When working with custom node and edge types, you can pass your union types to hooks such as useReactFlow.
import { useReactFlow } from '@xyflow/react';
function FlowUtilities() {
const { getNodes, getEdges } =
useReactFlow<AppNode, AppEdge>();
const logFlow = () => {
const nodes = getNodes();
const edges = getEdges();
console.log(nodes, edges);
};
return (
<button onClick={logFlow}>
Log Workflow
</button>
);
}
This ensures that the returned nodes and edges use your application's custom type definitions.
You can also use typed selectors with useStore when you need direct access to the React Flow store. For reactive node and edge data, hooks such as useNodes and useEdges are also useful.
8. Using Type Guards for Custom Nodes
Type guards help TypeScript identify the exact type of a node before accessing its data.
For example, suppose your workflow contains both number and text nodes. You can create a type guard to filter number nodes:
function isNumberNode(
node: AppNode
): node is NumberNode {
return node.type === 'number';
}
Then use it to filter your node collection:
const numberNodes = nodes.filter(isNumberNode);
Now TypeScript understands that every item in numberNodes is a NumberNode, so you can safely access its numeric data.
Type guards are especially useful when building workflow validation, node-specific operations, data processing, and conditional execution systems.
9. Best Practices for React Flow TypeScript Projects
When developing a large React Flow application, following a consistent type structure can make the codebase easier to scale.
Define node data separately: Create reusable data types for each custom node instead of using loosely typed objects.
Use discriminated unions: Give each custom node a unique type identifier, such as text, number, or condition.
Type callbacks explicitly: Use React Flow's exported callback types to keep event handlers consistent.
Create shared node and edge unions: Maintain centralized AppNode and AppEdge types so components and hooks use the same definitions.
Use type guards: Narrow node and edge types before accessing specialized properties.
Avoid unnecessary type assertions: Prefer proper type definitions and narrowing over repeatedly using as to bypass TypeScript checks.
These practices help prevent runtime errors and make complex visual workflow applications easier to maintain.
10. Build Type-Safe Workflow Editors with VisualFlow
Building a visual workflow editor requires more than displaying nodes and connecting edges. A production-ready application may include custom node components, conditional connections, dynamic node data, nested flows, automatic layouts, and interactive edge controls.
TypeScript helps organize these features by ensuring that every node and edge follows a predictable data structure.
VisualFlow provides React Flow examples, reusable UI patterns, and workflow-building inspiration for developers creating node-based applications. Explore VisualFlow to discover practical implementations and ideas for building scalable React Flow editors.
Conclusion
Using TypeScript with React Flow improves code reliability and makes complex node-based applications easier to develop. By defining custom node and edge types, creating union types, typing callbacks and hooks, and using type guards, developers can build more maintainable workflow editors.
Whether you are creating a simple flowchart or a full-scale automation platform, combining React Flow and TypeScript provides a strong foundation for scalable visual applications.