How Graphify's Obsidian Vault Export Generates Bidirectional Links
Graphify generates bidirectional links by treating every edge in its underlying undirected NetworkX graph as a reciprocal connection, automatically writing Obsidian wiki links ([[…]]) in both connected node files.
Graphify's Obsidian vault export feature transforms knowledge graphs into a folder of interlinked Markdown files. When you call to_obsidian from graphify/export.py, the tool creates bidirectional links between notes by leveraging the undirected nature of its nx.Graph data structure, ensuring that navigating your exported vault feels native to Obsidian's link-based interface.
Why Bidirectional Links Happen Automatically
The bidirectional linking is not explicitly coded as a pairwise operation. Instead, it emerges naturally from Graphify's use of an undirected NetworkX graph (nx.Graph). In this data structure, edges have no inherent direction, meaning an edge connecting Node A to Node B is identical to one connecting Node B to Node A.
When the to_obsidian function processes the graph, it retrieves neighbors for each node using G.neighbors(node_id). Because the graph is undirected, this method returns all adjacent nodes regardless of which node initiated the relationship. Consequently, when Node A's Markdown file is written, it contains a wiki link to Node B. When Node B's file is written, it contains a wiki link to Node A. This achieves true bidirectional linking without requiring explicit reverse-edge logic.
Filename Sanitization Ensures Link Validity
For bidirectional links to function in Obsidian, the filenames inside the wiki links must exactly match the actual files on disk. Graphify implements a two-stage sanitization process to guarantee this, even when node labels are long or contain special characters.
Safe Filename Generation (_cap_filename)
Node labels are first cleaned to remove unsafe characters, then capped to ≤ 200 bytes. If truncation occurs, the function appends a deterministic 8‑character hash to maintain uniqueness. This logic is implemented in graphify/export.py at lines 65‑73:
# Conceptual implementation from graphify/export.py
def _cap_filename(name, max_bytes=200):
"""Truncate to max_bytes and append hash if needed."""
# Returns safe filename ≤ 200 bytes + hash if truncated
pass
Collision Handling (_dedup_node_filenames)
After sanitizing labels, _dedup_node_filenames (lines 82‑100 in graphify/export.py) resolves any remaining filename collisions—such as case‑insensitive duplicates—by appending numeric suffixes. This ensures every node maps to a distinct file, preventing scenarios where two nodes might overwrite each other or create ambiguous wiki links.
Writing the Wiki Links
Inside the main export loop, to_obsidian constructs each node's "Connections" section by iterating through its neighbors and formatting them as Obsidian wiki links. According to the source code at graphify/export.py lines 521‑529, each connection appears as:
- [[<neighbor-filename>]] - `<relation>` [<confidence>]
The <neighbor-filename> is retrieved from the same node_filename mapping that already applied _cap_filename and _dedup_node_filenames. This ensures that even if a label was truncated to fit filesystem limits, the wiki link points to an existing file. Because the underlying nx.Graph is undirected, the neighbor's own file will contain a reciprocal link back to the original node, creating the bidirectional structure.
Practical Example
The following code demonstrates how a single undirected edge becomes reciprocal wiki links in the exported vault:
import networkx as nx
from graphify.export import to_obsidian
import tempfile
import pathlib
# Build a simple undirected graph
G = nx.Graph()
G.add_node("A", label="Alpha")
G.add_node("B", label="Beta")
G.add_edge("A", "B", relation="calls", confidence="EXTRACTED")
# Export to temporary directory
out_dir = pathlib.Path(tempfile.mkdtemp())
to_obsidian(G, {0: ["A", "B"]}, str(out_dir))
# Result:
# Alpha.md contains: - [[Beta]] - `calls` [EXTRACTED]
# Beta.md contains: - [[Alpha]] - `calls` [EXTRACTED]
In this example, Alpha.md links to Beta.md and vice versa, allowing seamless navigation in Obsidian's graph view.
Summary
- Undirected Structure: Graphify uses
nx.Graphfrom NetworkX, whereG.neighbors(node_id)returns all adjacent nodes, causing every edge to be written in both incident node files. - Safe Filenames: The
_cap_filenamefunction (lines 65‑73) ensures labels fit within 200 bytes with deterministic hashing, while_dedup_node_filenames(lines 82‑100) prevents file collisions. - Automatic Reciprocity: Because the graph is undirected, the
to_obsidianfunction inherently creates reciprocal[[…]]wiki links without explicit reverse-edge logic. - Validation: Tests in
tests/test_obsidian_filename_cap.pyandtests/test_obsidian_dangling_member.pyverify that no dangling links are produced after filename truncation.
Frequently Asked Questions
Does Graphify support unidirectional links in Obsidian exports?
No. Because Graphify stores relationships in an undirected nx.Graph structure as implemented in graphify/export.py, every edge is treated as bidirectional. Both nodes always reference each other in their Connections sections, regardless of the original extraction direction.
What happens if node labels are identical except for capitalization?
The _dedup_node_filenames function resolves such collisions by appending numeric suffixes to the filenames. This ensures that wiki links like [[Node]] and [[Node 1]] point to distinct files, preventing broken links in the Obsidian vault.
How does Graphify handle extremely long node labels?
The _cap_filename function truncates labels to 200 bytes and appends an 8‑character hash to maintain uniqueness. The wiki link generation uses this same truncated filename, ensuring that [[…]] references resolve correctly even when the original label was shortened.
Where are the tests for bidirectional link generation?
The test suite includes tests/test_obsidian_filename_cap.py (verifying links resolve after truncation) and tests/test_obsidian_dangling_member.py (ensuring no dangling references exist). These confirm that the bidirectional links generated by to_obsidian always point to valid files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →