Graph format

Reference for the GraphNode and GraphEdge types used to represent entities and relationships in the Flowsint graph. Understanding these structures is essential for working with the frontend visualization and the graph database layer.

Overview

Flowsint's graph visualization is built on two core data structures: GraphNode (representing entities) and GraphEdge (representing relationships between entities). These types are defined in flowsint-app/src/types/graph.ts and are used throughout the frontend for rendering, interaction, and data management.

Understanding these structures is important when:

  • Building enrichers that create nodes and relationships
  • Working on the frontend visualization
  • Debugging graph rendering issues
  • Extending the graph with new visual features

GraphNode

A GraphNode represents a single entity in the graph (e.g., a domain, an IP address, a person). Every node created by an enricher's create_node() method ends up as a GraphNode in the frontend.

type GraphNode = {
  id: string
  nodeType: string
  nodeLabel: string
  nodeProperties: NodeProperties
  nodeSize: number
  nodeColor: string | null
  nodeIcon: keyof typeof LucideIcons | null
  nodeImage: string | null
  nodeFlag: flagColor | null
  nodeShape: NodeShape | null
  nodeMetadata: NodeMetadata
  x: number
  y: number
  val?: number
  neighbors?: any[]
  links?: any[]
}

Field reference

FieldTypeDescription
idstringUnique identifier for the node. Derived from the type's primary field value.
nodeTypestringThe entity type (e.g., "Domain", "Ip", "Email"). Maps to the Pydantic type class name.
nodeLabelstringHuman-readable label displayed on the node. Set by the type's compute_label() method.
nodePropertiesNodePropertiesAll properties of the entity as key-value pairs. Contains the serialized fields from the Pydantic type.
nodeSizenumberVisual size of the node in the graph.
nodeColorstring | nullCustom color override for the node (CSS color string), or null for default.
nodeIconkeyof LucideIcons | nullIcon to display on the node, from the Lucide icon set, or null for default.
nodeImagestring | nullURL to an image to display on the node (e.g., a profile picture), or null.
nodeFlagflagColor | nullColor flag for visual tagging (see Flag colors below), or null.
nodeShapeNodeShape | nullShape of the node (see Node shapes below), or null for default circle.
nodeMetadataNodeMetadataAdditional metadata as key-value pairs. Used for internal tracking and extended features.
xnumberX coordinate position in the graph canvas.
ynumberY coordinate position in the graph canvas.
valnumber (optional)Value used by the force-graph engine for sizing calculations.
neighborsany[] (optional)Adjacent nodes, populated by the graph engine at runtime.
linksany[] (optional)Connected edges, populated by the graph engine at runtime.

NodeProperties

type NodeProperties = {
  [key: string]: any
}

A flexible key-value object containing all the entity's properties. When a Pydantic type is serialized into a node, its fields become entries in nodeProperties. For example, a Domain node would have nodeProperties.domain, nodeProperties.root, etc.

NodeMetadata

type NodeMetadata = {
  [key: string]: any
}

Similar to nodeProperties but used for system-level metadata rather than entity data. This can include information like creation timestamps, source enricher, or other tracking data.

Node shapes

Nodes can be rendered in four shapes:

type NodeShape = 'circle' | 'square' | 'hexagon' | 'triangle'
ShapeUse case
circleDefault shape for most entity types
squareOften used for infrastructure entities
hexagonUsed for grouped or aggregate entities
triangleUsed for alert or warning-related entities

Flag colors

Flags provide a visual tagging system for nodes. Users can flag nodes to highlight them during an investigation.

type flagColor = 'red' | 'orange' | 'blue' | 'green' | 'yellow'

Each flag color maps to specific Tailwind CSS classes for consistent styling:

FlagStyle
redtext-red-400 fill-red-200
orangetext-orange-400 fill-orange-200
bluetext-blue-400 fill-blue-200
greentext-green-400 fill-green-200
yellowtext-yellow-400 fill-yellow-200

GraphEdge

A GraphEdge represents a relationship between two nodes (e.g., "RESOLVES_TO", "HAS_SUBDOMAIN", "FOUND_IN_BREACH"). Every relationship created by an enricher's create_relationship() method ends up as a GraphEdge in the frontend.

type GraphEdge = {
  source: GraphNode['id']
  target: GraphNode['id']
  date?: string
  id: string
  label: string
  caption?: string
  type?: string
  weight?: number
  confidence_level?: number | string
}

Field reference

FieldTypeDescription
sourcestringID of the source node (the "from" node in the relationship).
targetstringID of the target node (the "to" node in the relationship).
idstringUnique identifier for this edge.
labelstringDisplay label for the edge (e.g., "RESOLVES_TO", "HAS_EMAIL"). This is the relationship type string passed to create_relationship().
datestring (optional)Timestamp associated with the relationship (ISO 8601 format).
captionstring (optional)Additional descriptive text displayed on or near the edge.
typestring (optional)Relationship classification type for filtering or styling.
weightnumber (optional)Numerical weight of the relationship, can be used for visual thickness or importance ranking.
confidence_levelnumber | string (optional)Confidence score for the relationship, indicating how reliable the connection is.

How enrichers map to the graph

When you write an enricher and call graph methods in postprocess(), here's how the data flows:

Creating nodes

# In your enricher's postprocess method
self.create_node(domain)  # domain is a Pydantic Domain object

The graph service automatically:

  1. Extracts the type name from the Pydantic class (e.g., "Domain") -> nodeType
  2. Reads the primary field value (e.g., domain.domain) -> used for id
  3. Reads the nodeLabel field (set by compute_label()) -> nodeLabel
  4. Serializes all fields into nodeProperties
  5. Sets defaults for visual properties (nodeSize, nodeColor, etc.)

Creating relationships

# In your enricher's postprocess method
self.create_relationship(domain, ip, "RESOLVES_TO")

The graph service automatically:

  1. Identifies the source node from domain's type and primary field -> source
  2. Identifies the target node from ip's type and primary field -> target
  3. Uses the relationship label "RESOLVES_TO" -> label
  4. Generates a unique id for the edge

Relationship naming conventions

Relationship labels follow these conventions:

  • Use UPPER_SNAKE_CASE (e.g., "RESOLVES_TO", "HAS_SUBDOMAIN")
  • Use verb phrases that describe the relationship direction (source -> target)
  • The default relationship label is "IS_RELATED_TO" if none is specified
  • Common patterns:
    • HAS_* for ownership/containment (e.g., "HAS_EMAIL", "HAS_SUBDOMAIN")
    • RESOLVES_TO for DNS-type lookups
    • FOUND_IN_* for breach/leak associations
    • REGISTERED_BY for WHOIS data

Graph settings

The graph visualization is configurable through settings types:

ForceGraphSetting

Controls the physics simulation of the force-directed graph layout:

type ForceGraphSetting = {
  value: any
  min?: number
  max?: number
  step?: number
  type?: string
  description?: string
}

ExtendedSetting

General-purpose settings with richer type information:

type ExtendedSetting = {
  value: any
  type: string
  min?: number
  max?: number
  step?: number
  options?: { value: string; label: string }[]
  description?: string
}

These settings allow users to customize graph behavior like node repulsion, link distance, simulation speed, and visual preferences.

Reserved properties

When creating nodes, certain property names are reserved by the system and should not be used as field names in your custom types:

  • id - Node identifier
  • x, y - Position coordinates
  • nodeLabel - Display label (set by compute_label())
  • label - Legacy label field
  • nodeType, type - Entity type identifiers
  • nodeImage, nodeIcon, nodeColor, nodeSize - Visual properties
  • created_at - Creation timestamp
  • sketch_id - Investigation sketch association
v1.2.7·Last updated Feb 15, 2025

Need troubleshooting or spotted a bug ? Feel free to submit an issue here.