Skip to Content
DevelopersConceptsJourney graph schema

Journey graph schema

Journeys are stored as directed acyclic graphs (DAGs) — a list of nodes connected by edges. The same schema is enforced by JourneyValidator._validate_graph_schema() at write time and is used by the journey builder UI, by playbook component templates (component_templates.config), and by inline playbook definitions (playbook_templates.journey_config).

Node schema

{ "id": "node_abc123", "type": "send", "label": "Send welcome email", "config": { "channel": "email", "template_id": "tmpl_welcome_v2" }, "next_nodes": [], "position": { "x": 200, "y": 400 } }
FieldTypeDescription
idstringUnique within the graph
typestringNode type — always lowercase_snake_case (see types below)
labelstringOptional human-readable label
configobjectType-specific configuration
next_nodesarrayDeprecated — always empty. Routing is via edges.
positionobjectCanvas coordinates for the visual builder

Node types

Node type values are a fixed enum of lowercase snake_case strings. Common types used in most journeys:

TypePurposeConfig fields
event_triggerEntry point — fires on an eventevent_name, re_entry, optional event filters
segment_triggerEntry point — fires on segment entry/exitsegment_id, direction
date_triggerEntry point — schedule-basedcron, timezone
api_triggerEntry point — external API pushtrigger key
waitPausewait_type (duration/until_event/until_time/dynamic), duration, timeout
sendDeliver messagechannel, template_id, fallback_channel, quiet_hours
in_app_messageQueue an in-app messagetemplate_id, surface
branchConditional splitconditions, default_path
experiment_splitA/B testvariants (array with percentage), winner_metric
parallel_forkRun multiple downstream paths concurrentlybranches
exitEnd journeyexit_reason (optional label)

Additional specialised types cover loyalty (loyalty_check_balance, loyalty_award_points, …), gamification, AI checks (contact_intelligence_check, send_time_optimization, …), audience operations (audience_sync, segment_move, …), and ad creative orchestration. The visual journey builder lists every available node type in its sidebar; that catalogue is authoritative.

Edge schema

{ "id": "edge_node_1_to_node_2_0", "source_node_id": "node_1", "target_node_id": "node_2", "label": "true", "routing": [{ "x": 150, "y": 250 }] }
FieldTypeDescription
idstringUnique within the graph
source_node_idstringID of the source node
target_node_idstringID of the target node
labelstringOutput port label — e.g., "true", "false", "timeout", "variant_a"
routingarrayOptional canvas waypoints for edge rendering

Rules

  1. Node types are always lowercase_snake_case — never UPPERCASE. The string must match a value in the NodeType enum.
  2. Routing is always via edges — never via next_nodes on nodes (deprecated field, always empty).
  3. Edges are dumb — they carry labels (e.g. "true", "false", "timeout", "variant_a"), not logic. Branch evaluation lives in the branch node’s config, not on the edge.
  4. No cycles — the graph must be a DAG (directed acyclic graph).
  5. Exactly one trigger per journey — exactly one node whose type is a trigger variant (event_trigger, segment_trigger, date_trigger, api_trigger, operational_event_trigger).
  6. Every path must terminate — either at an exit node or at a node with no outgoing edges.

Wait polymorphism

Wait nodes support four wait_type values:

Wait typeBehavior
durationPause for a fixed time (minutes, hours, days)
until_eventPause until the contact fires a specific event (with optional timeout)
until_timePause until a specific date/time or contact property date
dynamicPause for a duration computed from a contact/event property

Validation

The journey API validates the graph schema on every save:

  • All node IDs must be unique
  • All edge source/target IDs must reference existing nodes
  • Node types must be from the allowed set
  • Config must match the schema for the node type
  • No cycles allowed

Invalid graphs are rejected with descriptive error messages.

Where else this schema applies

The same node/edge shape is reused outside of journeys:

  • component_templates.config — control-plane catalog of reusable journey components
  • playbook_templates.journey_config — inline journey definitions inside playbook templates

Validation is the same in every location.

What’s next