# JSON Canvas Skill This skill enables Claude Code to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications. ## Overview JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/). ## File Structure A canvas file contains two top-level arrays: ```json { "nodes": [], "edges": [] } ``` - `nodes` (optional): Array of node objects - `edges` (optional): Array of edge objects connecting nodes ## Nodes Nodes are objects placed on the canvas. There are four node types: - `text` - Text content with Markdown - `file` - Reference to files/attachments - `link` - External URL - `group` - Visual container for other nodes ### Z-Index Ordering Nodes are ordered by z-index in the array: - First node = bottom layer (displayed below others) - Last node = top layer (displayed above others) ### Generic Node Attributes All nodes share these attributes: | Attribute | Required | Type | Description | |-----------|----------|------|-------------| | `id` | Yes | string | Unique identifier for the node | | `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` | | `x` | Yes | integer | X position in pixels | | `y` | Yes | integer | Y position in pixels | | `width` | Yes | integer | Width in pixels | | `height` | Yes | integer | Height in pixels | | `color` | No | canvasColor | Node color (see Color section) | ### Text Nodes Text nodes contain Markdown content. ```json { "id": "6f0ad84f44ce9c17", "type": "text", "x": 0, "y": 0, "width": 400, "height": 200, "text": "# Hello World\n\nThis is **Markdown** content." } ``` | Attribute | Required | Type | Description | |-----------|----------|------|-------------| | `text` | Yes | string | Plain text with Markdown syntax | ### File Nodes File nodes reference files or attachments (images, videos, PDFs, notes, etc.). ```json { "id": "a1b2c3d4e5f67890", "type": "file", "x": 500, "y": 0, "width": 400, "height": 300, "file": "Attachments/diagram.png" } ``` ```json { "id": "b2c3d4e5f6789012", "type": "file", "x": 500, "y": 400, "width": 400, "height": 300, "file": "Notes/Project Overview.md", "subpath": "#Implementation" } ``` | Attribute | Required | Type | Description | |-----------|----------|------|-------------| | `file` | Yes | string | Path to file within the system | | `subpath` | No | string | Link to heading or block (starts with `#`) | ### Link Nodes Link nodes display external URLs. ```json { "id": "c3d4e5f678901234", "type": "link", "x": 1000, "y": 0, "width": 400, "height": 200, "url": "https://obsidian.md" } ``` | Attribute | Required | Type | Description | |-----------|----------|------|-------------| | `url` | Yes | string | External URL | ### Group Nodes Group nodes are visual containers for organizing other nodes. ```json { "id": "d4e5f6789012345a", "type": "group", "x": -50, "y": -50, "width": 1000, "height": 600, "label": "Project Overview", "color": "4" } ``` ```json { "id": "e5f67890123456ab", "type": "group", "x": 0, "y": 700, "width": 800, "height": 500, "label": "Resources", "background": "Attachments/background.png", "backgroundStyle": "cover" } ``` | Attribute | Required | Type | Description | |-----------|----------|------|-------------| | `label` | No | string | Text label for the group | | `background` | No | string | Path to background image | | `backgroundStyle` | No | string | Background rendering style | #### Background Styles | Value | Description | |-------|-------------| | `cover` | Fills entire width and height of node | | `ratio` | Maintains aspect ratio of background image | | `repeat` | Repeats image as pattern in both directions | ## Edges Edges are lines connecting nodes. ```json { "id": "f67890123456789a", "fromNode": "6f0ad84f44ce9c17", "toNode": "a1b2c3d4e5f67890" } ``` ```json { "id": "0123456789abcdef", "fromNode": "6f0ad84f44ce9c17", "fromSide": "right", "fromEnd": "none", "toNode": "b2c3d4e5f6789012", "toSide": "left", "toEnd": "arrow", "color": "1", "label": "leads to" } ``` | Attribute | Required | Type | Default | Description | |-----------|----------|------|---------|-------------| | `id` | Yes | string | - | Unique identifier for the edge | | `fromNode` | Yes | string | - | Node ID where connection starts | | `fromSide` | No | string | - | Side where edge starts | | `fromEnd` | No | string | `none` | Shape at edge start | | `toNode` | Yes | string | - | Node ID where connection ends | | `toSide` | No | string | - | Side where edge ends | | `toEnd` | No | string | `arrow` | Shape at edge end | | `color` | No | canvasColor | - | Line color | | `label` | No | string | - | Text label for the edge | ### Side Values | Value | Description | |-------|-------------| | `top` | Top edge of node | | `right` | Right edge of node | | `bottom` | Bottom edge of node | | `left` | Left edge of node | ### End Shapes | Value | Description | |-------|-------------| | `none` | No endpoint shape | | `arrow` | Arrow endpoint | ## Colors The `canvasColor` type can be specified in two ways: ### Hex Colors ```json { "color": "#FF0000" } ``` ### Preset Colors ```json { "color": "1" } ``` | Preset | Color | |--------|-------| | `"1"` | Red | | `"2"` | Orange | | `"3"` | Yellow | | `"4"` | Green | | `"5"` | Cyan | | `"6"` | Purple | Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors. ## Complete Examples ### Simple Canvas with Text and Connections ```json { "nodes": [ { "id": "8a9b0c1d2e3f4a5b", "type": "text", "x": 0, "y": 0, "width": 300, "height": 150, "text": "# Main Idea\n\nThis is the central concept." }, { "id": "1a2b3c4d5e6f7a8b", "type": "text", "x": 400, "y": -100, "width": 250, "height": 100, "text": "## Supporting Point A\n\nDetails here." }, { "id": "2b3c4d5e6f7a8b9c", "type": "text", "x": 400, "y": 100, "width": 250, "height": 100, "text": "## Supporting Point B\n\nMore details." } ], "edges": [ { "id": "3c4d5e6f7a8b9c0d", "fromNode": "8a9b0c1d2e3f4a5b", "fromSide": "right", "toNode": "1a2b3c4d5e6f7a8b", "toSide": "left" }, { "id": "4d5e6f7a8b9c0d1e", "fromNode": "8a9b0c1d2e3f4a5b", "fromSide": "right", "toNode": "2b3c4d5e6f7a8b9c", "toSide": "left" } ] } ``` ### Project Board with Groups ```json { "nodes": [ { "id": "5e6f7a8b9c0d1e2f", "type": "group", "x": 0, "y": 0, "width": 300, "height": 500, "label": "To Do", "color": "1" }, { "id": "6f7a8b9c0d1e2f3a", "type": "group", "x": 350, "y": 0, "width": 300, "height": 500, "label": "In Progress", "color": "3" }, { "id": "7a8b9c0d1e2f3a4b", "type": "group", "x": 700, "y": 0, "width": 300, "height": 500, "label": "Done", "color": "4" }, { "id": "8b9c0d1e2f3a4b5c", "type": "text", "x": 20, "y": 50, "width": 260, "height": 80, "text": "## Task 1\n\nImplement feature X" }, { "id": "9c0d1e2f3a4b5c6d", "type": "text", "x": 370, "y": 50, "width": 260, "height": 80, "text": "## Task 2\n\nReview PR #123", "color": "2" }, { "id": "0d1e2f3a4b5c6d7e", "type": "text", "x": 720, "y": 50, "width": 260, "height": 80, "text": "## Task 3\n\n~~Setup CI/CD~~" } ], "edges": [] } ``` ### Research Canvas with Files and Links ```json { "nodes": [ { "id": "1e2f3a4b5c6d7e8f", "type": "text", "x": 300, "y": 200, "width": 400, "height": 200, "text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?", "color": "5" }, { "id": "2f3a4b5c6d7e8f9a", "type": "file", "x": 0, "y": 0, "width": 250, "height": 150, "file": "Literature/Paper A.pdf" }, { "id": "3a4b5c6d7e8f9a0b", "type": "file", "x": 0, "y": 200, "width": 250, "height": 150, "file": "Notes/Meeting Notes.md", "subpath": "#Key Insights" }, { "id": "4b5c6d7e8f9a0b1c", "type": "link", "x": 0, "y": 400, "width": 250, "height": 100, "url": "https://example.com/research" }, { "id": "5c6d7e8f9a0b1c2d", "type": "file", "x": 750, "y": 150, "width": 300, "height": 250, "file": "Attachments/diagram.png" } ], "edges": [ { "id": "6d7e8f9a0b1c2d3e", "fromNode": "2f3a4b5c6d7e8f9a", "fromSide": "right", "toNode": "1e2f3a4b5c6d7e8f", "toSide": "left", "label": "supports" }, { "id": "7e8f9a0b1c2d3e4f", "fromNode": "3a4b5c6d7e8f9a0b", "fromSide": "right", "toNode": "1e2f3a4b5c6d7e8f", "toSide": "left", "label": "informs" }, { "id": "8f9a0b1c2d3e4f5a", "fromNode": "4b5c6d7e8f9a0b1c", "fromSide": "right", "toNode": "1e2f3a4b5c6d7e8f", "toSide": "left", "toEnd": "arrow", "color": "6" }, { "id": "9a0b1c2d3e4f5a6b", "fromNode": "1e2f3a4b5c6d7e8f", "fromSide": "right", "toNode": "5c6d7e8f9a0b1c2d", "toSide": "left", "label": "visualized by" } ] } ``` ### Flowchart ```json { "nodes": [ { "id": "a0b1c2d3e4f5a6b7", "type": "text", "x": 200, "y": 0, "width": 150, "height": 60, "text": "**Start**", "color": "4" }, { "id": "b1c2d3e4f5a6b7c8", "type": "text", "x": 200, "y": 100, "width": 150, "height": 60, "text": "Step 1:\nGather data" }, { "id": "c2d3e4f5a6b7c8d9", "type": "text", "x": 200, "y": 200, "width": 150, "height": 80, "text": "**Decision**\n\nIs data valid?", "color": "3" }, { "id": "d3e4f5a6b7c8d9e0", "type": "text", "x": 400, "y": 200, "width": 150, "height": 60, "text": "Process data" }, { "id": "e4f5a6b7c8d9e0f1", "type": "text", "x": 0, "y": 200, "width": 150, "height": 60, "text": "Request new data", "color": "1" }, { "id": "f5a6b7c8d9e0f1a2", "type": "text", "x": 400, "y": 320, "width": 150, "height": 60, "text": "**End**", "color": "4" } ], "edges": [ { "id": "a6b7c8d9e0f1a2b3", "fromNode": "a0b1c2d3e4f5a6b7", "fromSide": "bottom", "toNode": "b1c2d3e4f5a6b7c8", "toSide": "top" }, { "id": "b7c8d9e0f1a2b3c4", "fromNode": "b1c2d3e4f5a6b7c8", "fromSide": "bottom", "toNode": "c2d3e4f5a6b7c8d9", "toSide": "top" }, { "id": "c8d9e0f1a2b3c4d5", "fromNode": "c2d3e4f5a6b7c8d9", "fromSide": "right", "toNode": "d3e4f5a6b7c8d9e0", "toSide": "left", "label": "Yes", "color": "4" }, { "id": "d9e0f1a2b3c4d5e6", "fromNode": "c2d3e4f5a6b7c8d9", "fromSide": "left", "toNode": "e4f5a6b7c8d9e0f1", "toSide": "right", "label": "No", "color": "1" }, { "id": "e0f1a2b3c4d5e6f7", "fromNode": "e4f5a6b7c8d9e0f1", "fromSide": "top", "fromEnd": "none", "toNode": "b1c2d3e4f5a6b7c8", "toSide": "left", "toEnd": "arrow" }, { "id": "f1a2b3c4d5e6f7a8", "fromNode": "d3e4f5a6b7c8d9e0", "fromSide": "bottom", "toNode": "f5a6b7c8d9e0f1a2", "toSide": "top" } ] } ``` ## ID Generation Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs: ```json "id": "6f0ad84f44ce9c17" "id": "a3b2c1d0e9f8g7h6" "id": "1234567890abcdef" ``` This format is a 16-character lowercase hex string (64-bit random value). ## Layout Guidelines ### Positioning - Coordinates can be negative (canvas extends infinitely) - `x` increases to the right - `y` increases downward - Position refers to top-left corner of node ### Recommended Sizes | Node Type | Suggested Width | Suggested Height | |-----------|-----------------|------------------| | Small text | 200-300 | 80-150 | | Medium text | 300-450 | 150-300 | | Large text | 400-600 | 300-500 | | File preview | 300-500 | 200-400 | | Link preview | 250-400 | 100-200 | | Group | Varies | Varies | ### Spacing - Leave 20-50px padding inside groups - Space nodes 50-100px apart for readability - Align nodes to grid (multiples of 10 or 20) for cleaner layouts ## Validation Rules 1. All `id` values must be unique across nodes and edges 2. `fromNode` and `toNode` must reference existing node IDs 3. Required fields must be present for each node type 4. `type` must be one of: `text`, `file`, `link`, `group` 5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat` 6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left` 7. `fromEnd`, `toEnd` must be one of: `none`, `arrow` 8. Color presets must be `"1"` through `"6"` or valid hex color ## References - [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/) - [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)