Skip to main content

Overview

The @statelyai/graph library uses plain JSON-serializable objects to represent graphs. There are no classes required—you can create graphs using simple object literals or factory functions.

Basic Structure

A graph consists of three main components:

Nodes

Vertices in the graph with an ID and optional data

Edges

Connections between nodes with source and target

Graph

Container with metadata and configuration

Graph Type

The Graph interface defines the core structure:
interface Graph<TNodeData = any, TEdgeData = any, TGraphData = any> {
  id: string;
  type: 'directed' | 'undirected';
  initialNodeId: string | null;
  nodes: GraphNode<TNodeData>[];
  edges: GraphEdge<TEdgeData>[];
  data: TGraphData;
  direction?: 'up' | 'down' | 'left' | 'right';
  style?: Record<string, string | number>;
}

Properties

  • id — Unique identifier for the graph
  • type — Either 'directed' or 'undirected'
  • initialNodeId — Starting node for traversals (nullable)
  • nodes — Array of graph nodes
  • edges — Array of graph edges
  • data — Custom metadata attached to the graph
  • direction — Layout direction hint for rendering
  • style — Custom styles for visual rendering

Node Type

Nodes represent vertices in the graph:
interface GraphNode<TNodeData = any> {
  type: 'node';
  id: string;
  parentId?: string | null;
  initialNodeId?: string | null;
  label: string;
  data: TNodeData;
  // Optional visual properties
  x?: number;
  y?: number;
  width?: number;
  height?: number;
  shape?: string;
  color?: string;
  style?: Record<string, string | number>;
}

Properties

  • type — Always 'node' for type discrimination
  • id — Unique identifier (required, non-empty string)
  • parentId — Parent node ID for hierarchical graphs (see Hierarchical Graphs)
  • initialNodeId — Starting child node for compound nodes
  • label — Display label (defaults to empty string)
  • data — Custom data payload
  • Visual properties — Optional position, size, shape, color, and styles

Edge Type

Edges represent connections between nodes:
interface GraphEdge<TEdgeData = any> {
  type: 'edge';
  id: string;
  sourceId: string;
  targetId: string;
  label: string;
  data: TEdgeData;
  // Optional visual properties
  x?: number;
  y?: number;
  width?: number;
  height?: number;
  color?: string;
  style?: Record<string, string | number>;
}

Properties

  • type — Always 'edge' for type discrimination
  • id — Unique identifier (required, non-empty string)
  • sourceId — ID of the source node
  • targetId — ID of the target node
  • label — Display label (defaults to empty string)
  • data — Custom data payload
  • Visual properties — Optional position, size, color, and styles

Creating Graphs

Using the Factory Function

The recommended way to create graphs is with createGraph():
import { createGraph } from '@statelyai/graph';

const graph = createGraph({
  id: 'my-graph',
  type: 'directed',
  nodes: [
    { id: 'a', label: 'Start' },
    { id: 'b', label: 'Middle' },
    { id: 'c', label: 'End' }
  ],
  edges: [
    { id: 'e1', sourceId: 'a', targetId: 'b', label: 'next' },
    { id: 'e2', sourceId: 'b', targetId: 'c', label: 'next' }
  ]
});

Using Plain Objects

Since graphs are plain objects, you can create them with literals:
import type { Graph } from '@statelyai/graph';

const graph: Graph = {
  id: 'manual',
  type: 'directed',
  initialNodeId: null,
  nodes: [
    {
      type: 'node',
      id: 'a',
      label: 'Node A',
      data: undefined
    }
  ],
  edges: [],
  data: undefined
};
Using createGraph() is recommended because it handles default values automatically. For example, label defaults to '', type defaults to 'directed', and visual properties are only included if provided.

Config vs. Primary Types

The library provides two sets of types:

Config Types (Input)

Lenient types for creating graphs—most fields are optional:
interface GraphConfig<TNodeData, TEdgeData, TGraphData> {
  id?: string;
  type?: 'directed' | 'undirected';
  nodes?: NodeConfig<TNodeData>[];
  edges?: EdgeConfig<TEdgeData>[];
  // ...
}

interface NodeConfig<TNodeData> {
  id: string; // Required
  label?: string;
  data?: TNodeData;
  // ...
}

interface EdgeConfig<TEdgeData> {
  id: string; // Required
  sourceId: string; // Required
  targetId: string; // Required
  label?: string;
  data?: TEdgeData;
  // ...
}

Primary Types (Output)

Strict types with all fields resolved:
interface Graph<TNodeData, TEdgeData, TGraphData> {
  id: string; // Always present
  type: 'directed' | 'undirected'; // Always present
  nodes: GraphNode<TNodeData>[]; // Always array
  edges: GraphEdge<TEdgeData>[]; // Always array
  // ...
}

TypeScript Generics

All graph types support three generic parameters:
Graph<TNodeData, TEdgeData, TGraphData>
  • TNodeData — Type of node.data
  • TEdgeData — Type of edge.data
  • TGraphData — Type of graph.data

Example with Custom Data

interface NodeData {
  value: number;
  visited: boolean;
}

interface EdgeData {
  weight: number;
}

const graph = createGraph<NodeData, EdgeData>({
  nodes: [
    { id: 'a', data: { value: 1, visited: false } },
    { id: 'b', data: { value: 2, visited: false } }
  ],
  edges: [
    { id: 'e1', sourceId: 'a', targetId: 'b', data: { weight: 5 } }
  ]
});

// TypeScript knows the data types
const nodeValue: number = graph.nodes[0].data.value;
const edgeWeight: number = graph.edges[0].data.weight;

Graph Wrapper Class

For an object-oriented API, use GraphInstance:
import { GraphInstance } from '@statelyai/graph';

const instance = new GraphInstance({
  nodes: [{ id: 'a' }, { id: 'b' }]
});

instance.addNode({ id: 'c' });
instance.addEdge({ id: 'e1', sourceId: 'a', targetId: 'c' });

console.log(instance.nodes.length); // 3
console.log(instance.edges.length); // 1

// Get the plain graph object
const plainGraph = instance.toJSON();
GraphInstance is just a wrapper around a plain Graph object. All methods delegate to standalone functions. The underlying graph is accessible via instance.graph.

Directed vs. Undirected Graphs

Directed Graphs

Edges have a clear direction from source to target:
const directed = createGraph({
  type: 'directed',
  nodes: [{ id: 'a' }, { id: 'b' }],
  edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }]
});

// a → b
// hasPath(directed, 'a', 'b') === true
// hasPath(directed, 'b', 'a') === false

Undirected Graphs

Edges can be traversed in both directions:
const undirected = createGraph({
  type: 'undirected',
  nodes: [{ id: 'a' }, { id: 'b' }],
  edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }]
});

// a — b
// hasPath(undirected, 'a', 'b') === true
// hasPath(undirected, 'b', 'a') === true

Examples

Simple Graph

const graph = createGraph({
  nodes: [
    { id: 'start', label: 'Start' },
    { id: 'end', label: 'End' }
  ],
  edges: [
    { id: 'transition', sourceId: 'start', targetId: 'end' }
  ]
});

Weighted Graph

interface WeightedEdge {
  weight: number;
}

const graph = createGraph<any, WeightedEdge>({
  nodes: [
    { id: 'a' },
    { id: 'b' },
    { id: 'c' }
  ],
  edges: [
    { id: 'e1', sourceId: 'a', targetId: 'b', data: { weight: 10 } },
    { id: 'e2', sourceId: 'b', targetId: 'c', data: { weight: 20 } },
    { id: 'e3', sourceId: 'a', targetId: 'c', data: { weight: 5 } }
  ]
});

Graph with Metadata

interface GraphMetadata {
  name: string;
  version: string;
  createdAt: Date;
}

const graph = createGraph<any, any, GraphMetadata>({
  id: 'workflow',
  data: {
    name: 'User Onboarding',
    version: '1.0.0',
    createdAt: new Date()
  },
  nodes: [{ id: 'welcome' }, { id: 'setup' }],
  edges: [{ id: 'e1', sourceId: 'welcome', targetId: 'setup' }]
});

Next Steps

Hierarchical Graphs

Learn about parent-child relationships and compound nodes

Visual Graphs

Add position and size for rendering

Serialization

Understand JSON serialization and plain objects

Operations

Explore graph operations (add, update, delete)

Build docs developers (and LLMs) love