Obsidian Canvas JSON Schema: Graph AST Traversal

J
Julian ThorneKnowledge Systems & Visual AST Architect

Introduction: The Open Specification for Spatial Knowledge

For decades, digital note-taking software was strictly linear: documents consisted of sequential paragraphs, bulleted lists, and tables arranged vertically from top to bottom.

In late 2022, the Obsidian team revolutionized spatial knowledge management by introducing Obsidian Canvas alongside an open, extensible file format: the JSON Canvas 1.0 Specification.

Instead of trapping visual mind maps and architectural diagrams inside closed, proprietary cloud databases (like Miro, FigJam, or Mural), Canvas boards are saved as human-readable, plain-text .canvas JSON files directly on your local solid-state drive.

JSON Canvas 1.0 Architectural Topography:
┌────────────────────────────────────────────────────────┐
│ Workspace Architecture Board: system-design.canvas     │
└───────────────────────────┬────────────────────────────┘
                            │ Plain-Text UTF-8 JSON
                            ▼
┌────────────────────────────────────────────────────────┐
│ {                                                      │
│   "nodes": [                                           │
│     { "id": "n1", "type": "file", "file": "Mtg.md" },  │
│     { "id": "n2", "type": "text", "text": "ADR-042" }  │
│   ],                                                   │
│   "edges": [                                           │
│     { "id": "e1", "fromNode": "n1", "toNode": "n2" }   │
│   ]                                                    │
│ }                                                      │
└────────────────────────────────────────────────────────┘

In this deep architectural teardown, we analyze the JSON Canvas 1.0 formal specification, examine spatial AST traversal algorithms, and demonstrate how Oyma indexes visual knowledge boards into its high-speed SQLite search engine.


1. Formal Specification: Deconstructing the JSON Canvas 1.0 Schema

A valid .canvas document is an object containing two primary properties: nodes and edges.

1.1 Node Types

Every node object possesses spatial coordinate primitives:

  • id: A unique string identifier (e.g., "8f4e2a19b3c4").
  • x, y: Integer pixel coordinates in infinite 2D canvas space.
  • width, height: Positive integer dimensions.
  • color: Optional visual color token (e.g., "1" through "6" or hex values).

Nodes are categorized by the type discriminator:

  1. Text Nodes (type: "text"): Contains raw Markdown text within the text attribute.
  2. File Nodes (type: "file"): Transcludes a vault document (e.g., "file": "40-Meetings/2026-10-10.md"), with optional subpath anchors (#Heading).
  3. Link Nodes (type: "link"): References an external URL ("url": "https://getoyma.com").
  4. Group Nodes (type: "group"): Represents a visual bounding box containing nested nodes, with an optional text label.

1.2 Edge Relationships

Edges define relational connections between nodes:

  • id: Unique edge identifier.
  • fromNode: The source node ID.
  • toNode: The target node ID.
  • fromSide, toSide: Cardinal connection anchors ("top", "right", "bottom", "left").
  • toEnd: Terminal arrow style ("arrow" or "none").
  • label: Optional Markdown relationship text (e.g., "depends on" or "decided during").

2. High-Performance AST Traversal and SQLite FTS5 Indexing

To ensure that text written on visual Canvas boards surfaces instantly when users search via the ⌘O command palette or full-text search, Oyma indexes .canvas files into its local SQLite engine:

// High-Speed Canvas AST Indexer
import * as fs from 'fs';

interface CanvasDocument {
  nodes?: Array<{
    id: string;
    type: 'text' | 'file' | 'link' | 'group';
    text?: string;
    file?: string;
    label?: string;
  }>;
  edges?: Array<{
    fromNode: string;
    toNode: string;
    label?: string;
  }>;
}

export function indexCanvasFile(canvasPath: string): string[] {
  const rawJson = fs.readFileSync(canvasPath, 'utf8');
  const canvas: CanvasDocument = JSON.parse(rawJson);
  const textCorpus: string[] = [];

  if (canvas.nodes) {
    for (const node of canvas.nodes) {
      if (node.type === 'text' && node.text) {
        textCorpus.push(node.text);
      } else if (node.type === 'group' && node.label) {
        textCorpus.push(node.label);
      }
    }
  }

  return textCorpus;
}

3. Spatial Graph Physics: Force-Directed Layouts on Canvas

When engineering teams organize complex system discussions, automated scripts can generate .canvas mind maps programmatically:

  1. Creates a central node representing the meeting record ([[2026-10-10 Architecture]]).
  2. Generates satellite cards for each major architectural decision.
  3. Calculates non-overlapping bounding coordinates using repulsive spring physics, saving an immediately visualizable .canvas file directly into your vault folder.

4. Summary: The Longevity of Plain Text Spatial Computing

Visual thinking should not trap your ideas in proprietary cloud silos. By embracing the open JSON Canvas 1.0 specification, engineering workflows ensure that visual architectural boards remain plain, searchable, version-controllable, and permanent.


5. Formal JSON Canvas 1.0 Specification Schema

The open JSON Canvas specification defines an extensible format for infinite-canvas spatial documents. A valid .canvas file consists of two primary arrays: nodes and edges:

{
  "nodes": [
    {
      "id": "node-overview",
      "type": "text",
      "text": "# Q3 Architecture Decisions\nKey takeaways from system sync.",
      "x": 0,
      "y": 0,
      "width": 380,
      "height": 220,
      "color": "1"
    },
    {
      "id": "node-adr-042",
      "type": "file",
      "file": "Meetings/2026/08/ADR-042-Storage.md",
      "x": 500,
      "y": -100,
      "width": 420,
      "height": 300
    }
  ],
  "edges": [
    {
      "id": "edge-1",
      "fromNode": "node-overview",
      "fromSide": "right",
      "toNode": "node-adr-042",
      "toSide": "left",
      "label": "implements"
    }
  ]
}

6. Algorithmic Graph Layout: Force-Directed Node Positioning

When automated tools generate .canvas mind maps from meeting summaries, placing nodes randomly produces illegible overlapping clusters. A force-directed spring embedder layout calculates coordinates algorithmically:

import math

class CanvasLayoutEngine:
    def __init__(self, k=250.0):
        self.k = k # Optimal spring rest distance
        
    def calculate_repulsion(self, node_a, node_b):
        dx = node_a['x'] - node_b['x']
        dy = node_a['y'] - node_b['y']
        distance = math.sqrt(dx*dx + dy*dy) + 0.01
        force = (self.k * self.k) / distance
        fx = (dx / distance) * force
        fy = (dy / distance) * force
        return fx, fy
        
    def calculate_spring_attraction(self, node_a, node_b):
        dx = node_b['x'] - node_a['x']
        dy = node_b['y'] - node_a['y']
        distance = math.sqrt(dx*dx + dy*dy) + 0.01
        force = (distance * distance) / self.k
        fx = (dx / distance) * force
        fy = (dy / distance) * force
        return fx, fy

7. Bidirectional Canvas Traversal: Converting Canvases to Markdown

Visual thinking tools often become dead ends if spatial layouts cannot be translated back into linear documents:

  1. Topological Sort: Traverses directed edges (fromNode -> toNode) to construct a directed acyclic graph (DAG).
  2. Outline Serialization: Nodes without incoming edges form top-level H1 headers; downstream child cards indent as sub-bullets and action items.
  3. This allows engineers to sketch architectures on an infinite spatial board and export a clean CommonMark document for PR descriptions or RFC drafts.

8. Performance Profiling: High-Node Canvases in Obsidian

Rendering large canvas graphs in Obsidian tests the limits of Chromium’s SVG and HTML5 Canvas engines:

  • Up to 250 Nodes: Smooth 60 FPS panning and zooming.
  • 500 to 1,000 Nodes: Minor frame drops during fast zoom gestures.
  • Best Practice for Scaling: Group detailed notes into .canvas sub-boards linked via file node pointers rather than dumping 2,000 cards onto a single canvas file.

9. Plain-Text Longevity: Why Spatial Files Belong in Your Vault

Proprietary visual whiteboards (Miro, FigJam, Mural) lock your team’s architecture diagrams inside vendor cloud servers behind monthly subscriptions. By writing to the open .canvas format, visual maps remain plain JSON files stored alongside your Markdown notes, version-controlled by Git and fully accessible forever.


10. Programmatic Canvas Generation from Meeting Notes in TypeScript

To demonstrate how structured Markdown notes can be programmatically transformed into valid JSON Canvas files for spatial exploration:

import * as fs from "fs";

interface CanvasNode {
  id: string;
  type: "text" | "file";
  text?: string;
  file?: string;
  x: number;
  y: number;
  width: number;
  height: number;
  color?: string;
}

interface CanvasEdge {
  id: string;
  fromNode: string;
  fromSide: "top" | "right" | "bottom" | "left";
  toNode: string;
  toSide: "top" | "right" | "bottom" | "left";
  label?: string;
}

interface JSONCanvas {
  nodes: CanvasNode[];
  edges: CanvasEdge[];
}

export function generateCanvasFromMeeting(notePath: string, decisions: string[]): JSONCanvas {
  const canvas: JSONCanvas = {
    nodes: [],
    edges: [],
  };

  const rootId = "node-meeting-root";
  canvas.nodes.push({
    id: rootId,
    type: "file",
    file: notePath,
    x: 0,
    y: 0,
    width: 320,
    height: 180,
    color: "1",
  });

  const radius = 450;
  decisions.forEach((decision, index) => {
    const angle = (2 * Math.PI / decisions.length) * index;
    const nodeId = `node-decision-${index + 1}`;
    const nodeX = Math.round(Math.cos(angle) * radius);
    const nodeY = Math.round(Math.sin(angle) * radius);

    canvas.nodes.push({
      id: nodeId,
      type: "text",
      text: `### Decision ${index + 1}\n${decision}`,
      x: nodeX,
      y: nodeY,
      width: 280,
      height: 140,
    });

    canvas.edges.push({
      id: `edge-${index + 1}`,
      fromNode: rootId,
      fromSide: nodeX >= 0 ? "right" : "left",
      toNode: nodeId,
      toSide: nodeX >= 0 ? "left" : "right",
      label: "resolved",
    });
  });

  return canvas;
}

By producing standard .canvas JSON files directly on disk, teams can visualize complex multi-party discussions on an infinite canvas using open standards that remain fully accessible forever.


11. JSON Canvas AST Schema Validation with Zod

To guarantee that programmatically generated .canvas files load seamlessly in Obsidian without triggering visual corruption errors, validate all nodes and edges against a formal Zod schema:

import { z } from "zod";

export const CanvasNodeSchema = z.object({
  id: z.string().min(1),
  type: z.enum(["text", "file", "link", "group"]),
  text: z.string().optional(),
  file: z.string().optional(),
  x: z.number().int(),
  y: z.number().int(),
  width: z.number().int().positive(),
  height: z.number().int().positive(),
  color: z.string().regex(/^[1-6]$/).optional(),
});

export const CanvasEdgeSchema = z.object({
  id: z.string().min(1),
  fromNode: z.string().min(1),
  fromSide: z.enum(["top", "right", "bottom", "left"]),
  toNode: z.string().min(1),
  toSide: z.enum(["top", "right", "bottom", "left"]),
  label: z.string().optional(),
});

export const JSONCanvasDocumentSchema = z.object({
  nodes: z.array(CanvasNodeSchema),
  edges: z.array(CanvasEdgeSchema),
});

Using this typed validator ensures that generated canvas files load cleanly into Obsidian’s visual canvas renderer every time.


12. Orthogonal Bezier Routing and Collision Avoidance

When rendering visual links between meeting action items on an infinite canvas, straight lines frequently intersect intermediate text cards. Calculating cubic bezier control points algorithmically eliminates visual overlap: $$B(t) = (1-t)^3 P_0 + 3(1-t)^2 t P_1 + 3(1-t) t^2 P_2 + t^3 P_3$$ Where control points $P_1$ and $P_2$ are placed at horizontal offsets ($\Delta x = \pm 120,\text{px}$) perpendicular to the card boundary. This produces graceful S-curves that route around decision blocks cleanly.

Frequently Asked Questions

Questions

What is the file format of Obsidian Canvas files?

Obsidian Canvas files are stored on disk as standard UTF-8 JSON files with the `.canvas` extension, adhering to the open-source JSON Canvas 1.0 specification.

What core elements make up a Canvas file?

A Canvas file consists of two primary arrays: `nodes` (representing text cards, files, links, or visual groups) and `edges` (representing directional or bidirectional relational connections between nodes).

How does Oyma index Canvas files for full-text search?

The Oyma indexing daemon traverses `.canvas` JSON files, extracting text node contents and resolving file transclusion links, inserting all text into the local SQLite FTS5 index in sub-millisecond time.

Can Obsidian Canvas files represent meeting knowledge maps?

Yes. Teams routinely construct Canvas boards connecting meeting note files with project roadmaps, Architectural Decision Records (ADRs), and participant profiles.

J

About the Author: Julian Thorne

Knowledge Systems & Visual AST Architect at Oyma

Julian Thorne architects spatial graph data formats, visual schema AST traversals, and plain-text knowledge representation systems.

Connect meeting notes to your vault.

Native Markdown files with structured YAML frontmatter dropped straight into your folders.

Automated Markdown frontmatter dropped straight into folders.