# How Graphify's Obsidian Vault Export Generates Bidirectional Links

> Graphify generates bidirectional Obsidian links by treating graph edges as reciprocal connections, automatically writing wiki links in connected node files. Explore the export feature.

- Repository: [Graphify Labs/graphify](https://github.com/Graphify-Labs/graphify)
- Tags: how-to-guide
- Published: 2026-07-15

---

**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`](https://github.com/Graphify-Labs/graphify/blob/main/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`](https://github.com/Graphify-Labs/graphify/blob/main/graphify/export.py) at lines 65‑73:

```python

# 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`](https://github.com/Graphify-Labs/graphify/blob/main/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`](https://github.com/Graphify-Labs/graphify/blob/main/graphify/export.py) lines 521‑529, each connection appears as:

```markdown
- [[<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:

```python
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`](https://github.com/Graphify-Labs/graphify/blob/main/Alpha.md) links to [`Beta.md`](https://github.com/Graphify-Labs/graphify/blob/main/Beta.md) and vice versa, allowing seamless navigation in Obsidian's graph view.

## Summary

- **Undirected Structure**: Graphify uses `nx.Graph` from NetworkX, where `G.neighbors(node_id)` returns all adjacent nodes, causing every edge to be written in both incident node files.
- **Safe Filenames**: The `_cap_filename` function (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_obsidian` function inherently creates reciprocal `[[…]]` wiki links without explicit reverse-edge logic.
- **Validation**: Tests in [`tests/test_obsidian_filename_cap.py`](https://github.com/Graphify-Labs/graphify/blob/main/tests/test_obsidian_filename_cap.py) and [`tests/test_obsidian_dangling_member.py`](https://github.com/Graphify-Labs/graphify/blob/main/tests/test_obsidian_dangling_member.py) verify 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`](https://github.com/Graphify-Labs/graphify/blob/main/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`](https://github.com/Graphify-Labs/graphify/blob/main/tests/test_obsidian_filename_cap.py) (verifying links resolve after truncation) and [`tests/test_obsidian_dangling_member.py`](https://github.com/Graphify-Labs/graphify/blob/main/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.