Section 3: Traversals and Direction

A node query tells you about individual objects. A traversal tells you about how objects are connected. This is where the graph becomes genuinely powerful — and where direction becomes critical.

What a traversal is

Imagine you want to find every interface on every leaf switch in the fabric. A node query can get you the leaf switches, and a separate node query can get you all interfaces — but neither tells you which interfaces belong to which switch.

A traversal walks from one node to another via a relationship, following the connections in the graph.

node(type='system', role='leaf')
  .out('hosted_interfaces')
  .node(type='interface')
Traversal result — leaf system nodes connected to their interface nodes via hosted_interfaces relationships

This query reads left to right:

  1. Start with all system nodes whose role is leaf.

  2. Follow the hosted_interfaces relationship outward from those nodes.

  3. Arrive at every interface node at the other end of those relationships.

The result is a subgraph: every leaf system connected to every interface it hosts. The DC1 - SE Demo blueprint has 3 leaf systems each with 15 interfaces, giving 45 interface results.

Table 1. Key properties of the interface node type
Property Example value What it means

if_name

ge-0/0/1

Physical port name — the exact string used in device config

if_type

ip, ethernet, logical_vtep

Port category

ipv4_addr

192.168.0.11/31

Assigned IP address (fabric-facing ports only)

operation_state

up, down

Current operational status of the port

l3_mtu

9170

MTU value

Direction: .out() and .in_()

Relationships in the graph are directional — they have a source end and a target end. When you traverse a relationship, you must specify which direction you are walking.

.out('type') — follow a relationship that departs from the current node (source → target)

.in_('type') — follow a relationship that arrives at the current node (target → source, walking backwards)

Diagram showing a system node with an arrow labelled hosted_interfaces pointing toward an interface node. .out() walks forward; .in_() walks backward.

Reading direction from the detail panel

The relationship detail panel, which you explored in Section 1, shows:

  • source_id — the node the relationship departs from (tail of the arrow)

  • target_id — the node the relationship points to (head of the arrow)

If a hosted_interfaces relationship has a system as its source and an interface as its target, then:

  • Starting from a system node, you use .out('hosted_interfaces') to reach the interface.

  • Starting from an interface node, you use .in_('hosted_interfaces') to reach the system it belongs to.

Apstra’s relationship directions are fixed and cannot be changed. If you write .out() when you should write .in_(), the query returns no results — silently. There is no error message. When a traversal returns nothing unexpected, checking the relationship direction is usually the first thing to investigate.

How to discover direction in your blueprint

  1. Open the Graph Explorer canvas.

  2. Find a node of the type you are starting from and a node of the type you want to reach.

  3. Click the line between them.

  4. Read the source_id from the relationship detail panel.

  5. Find the corresponding node on the canvas (paste the source ID into node(id='…​')) to confirm which type is the source.

Alternatively, use the Show reference design schema button to display a diagram of the main node types and their relationship directions.

Always specify the relationship type

You can call .out() and .in_() without a type argument:

node(type='system').out().node(type='interface')

This returns all interfaces connected to all systems via any outbound relationship — not just hosted_interfaces. In a fabric where systems have multiple types of outbound relationships, this query may return a much larger and less predictable result set than you intended.

Get into the habit of always specifying the relationship type:

node(type='system').out('hosted_interfaces').node(type='interface')

This is unambiguous and will not surprise you as the blueprint grows.

A reference to the common relationships

The table below covers the relationships you will use most frequently when querying a three-stage reference design. Direction is shown as source_type → target_type.

Relationship type Source Target What it connects

hosted_interfaces

system

interface

A switch or server to its physical ports

part_of_rack

system

rack

A system to the rack it sits in

hosted_vn_instances

system

vn_instance

A leaf switch to the VN instances it hosts

instantiates / instantiated_by

vn_instance

virtual_network

A VN instance to the virtual network it is an instantiation of

member_vns

security_zone

virtual_network

A routing zone to its member virtual networks

policy

security_zone

routing_policy

A routing zone to its routing policy

composed_of_systems

redundancy_group

system

An ESI/MLAG pair to its member switches

composed_of

interface

interface

A port-channel interface to its member physical interfaces

link

interface

link

A physical interface to the link object connecting it to another interface

interface_map

system

interface_map

A system to its interface mapping

device_profile

interface_map

device_profile

An interface map to its device profile

member_endpoints

virtual_network

vn_endpoint

A virtual network to its endpoint objects

hosted_vn_endpoints

interface

vn_endpoint

An interface to the VN endpoint hosted on it

This table covers the most common relationships. When you encounter one that is not listed here, click it in the Graph Explorer and read the type from the detail panel.

Building a traversal step by step

The best way to understand traversal construction is to build a query incrementally. The target: find every interface on every system in a specific rack.

Step 1 — Start with the rack

node(type='rack', label='rack1')
Single rack node selected

One rack node. Confirm the label matches a rack that exists in your blueprint.

Step 2 — Add the relationship to systems

node(type='rack', label='rack1')
  .in_('part_of_rack')
  .node(type='system')
Rack node connected to its system nodes via part_of_rack relationships

The part_of_rack relationship has system as its source and rack as its target. We are starting at the rack and walking backwards along those relationships — hence .in_('part_of_rack').

You now have the rack and all the systems in it.

Step 3 — Add the relationship to interfaces

node(type='rack', label='rack1')
  .in_('part_of_rack')
  .node(type='system')
  .out('hosted_interfaces')
  .node(type='interface')
Full traversal result — rack

Now the query reads:

  • Start at rack1.

  • Walk backwards along part_of_rack to reach all systems in that rack.

  • Walk forwards along hosted_interfaces to reach all interfaces on those systems.

This is the same query from the slides, made real with your blueprint. If your rack has two systems with seven and six interfaces respectively, you will see exactly that.

Extending the traversal

A traversal can be as long as you need. You can chain as many .out(), .in_(), .node() segments as required:

node(type='system', role='leaf', deploy_mode='deploy')
  .out('hosted_vn_instances')
  .node(type='vn_instance')
  .out('instantiates')
  .node(type='virtual_network')

This walks: deployed leaf → VN instances on that leaf → virtual networks those instances represent.

Each .node() in the chain can carry its own filters. Each .out() or .in_() can carry its own type. The chain is evaluated left to right, narrowing the result set at each step.

Checkpoint

Build and run the following traversals in your blueprint. In each case, inspect the result on the canvas and confirm the node counts make sense for your fabric.

All interfaces on all spine switches:

node(type='system', role='spine')
  .out('hosted_interfaces')
  .node(type='interface')

All systems in any rack (list all racks and their systems):

node(type='rack')
  .in_('part_of_rack')
  .node(type='system')

All virtual networks in any security zone:

node(type='security_zone')
  .out('member_vns')
  .node(type='virtual_network')

When you are comfortable building traversals, continue to Section 4: Property Matchers to learn how to apply more sophisticated filters than simple equality.