json-canvas · diff
git:20260116.1fe3a85 to git:20260225.5a557ce
94 added, 506 removed. Audit A to A.
---
name: json-canvas
description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
---
# JSON Canvas Skill
- This skill enables skills-compatible agents 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:
+ A canvas file (`.canvas`) contains two top-level arrays following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/):
```json
{
"nodes": [],
"edges": []
}
```
- `nodes` (optional): Array of node objects
- `edges` (optional): Array of edge objects connecting nodes
- ## Nodes
+ ## Common Workflows
- 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
+ ### 1. Create a New Canvas
- ### Z-Index Ordering
+ 1. Create a `.canvas` file with the base structure `{"nodes": [], "edges": []}`
+ 2. Generate unique 16-character hex IDs for each node (e.g., `"6f0ad84f44ce9c17"`)
+ 3. Add nodes with required fields: `id`, `type`, `x`, `y`, `width`, `height`
+ 4. Add edges referencing valid node IDs via `fromNode` and `toNode`
+ 5. **Validate**: Parse the JSON to confirm it is valid. Verify all `fromNode`/`toNode` values exist in the nodes array
- Nodes are ordered by z-index in the array:
- - First node = bottom layer (displayed below others)
- - Last node = top layer (displayed above others)
+ ### 2. Add a Node to an Existing Canvas
- ### Generic Node Attributes
+ 1. Read and parse the existing `.canvas` file
+ 2. Generate a unique ID that does not collide with existing node or edge IDs
+ 3. Choose position (`x`, `y`) that avoids overlapping existing nodes (leave 50-100px spacing)
+ 4. Append the new node object to the `nodes` array
+ 5. Optionally add edges connecting the new node to existing nodes
+ 6. **Validate**: Confirm all IDs are unique and all edge references resolve to existing nodes
- All nodes share these attributes:
+ ### 3. Connect Two Nodes
+ 1. Identify the source and target node IDs
+ 2. Generate a unique edge ID
+ 3. Set `fromNode` and `toNode` to the source and target IDs
+ 4. Optionally set `fromSide`/`toSide` (top, right, bottom, left) for anchor points
+ 5. Optionally set `label` for descriptive text on the edge
+ 6. Append the edge to the `edges` array
+ 7. **Validate**: Confirm both `fromNode` and `toNode` reference existing node IDs
+
+ ### 4. Edit an Existing Canvas
+
+ 1. Read and parse the `.canvas` file as JSON
+ 2. Locate the target node or edge by `id`
+ 3. Modify the desired attributes (text, position, color, etc.)
+ 4. Write the updated JSON back to the file
+ 5. **Validate**: Re-check all ID uniqueness and edge reference integrity after editing
+
+ ## Nodes
+
+ Nodes are objects placed on the canvas. Array order determines z-index: first node = bottom layer, last node = top layer.
+
+ ### Generic Node Attributes
+
| Attribute | Required | Type | Description |
|-----------|----------|------|-------------|
- | `id` | Yes | string | Unique identifier for the node |
- | `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` |
+ | `id` | Yes | string | Unique 16-char hex identifier |
+ | `type` | Yes | string | `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) |
+ | `color` | No | canvasColor | Preset `"1"`-`"6"` or hex (e.g., `"#FF0000"`) |
### Text Nodes
- Text nodes contain Markdown content.
+ | Attribute | Required | Type | Description |
+ |-----------|----------|------|-------------|
+ | `text` | Yes | string | Plain text with Markdown syntax |
```json
{
"id": "6f0ad84f44ce9c17",
"type": "text",
"x": 0,
"y": 0,
"width": 400,
"height": 200,
"text": "# Hello World\n\nThis is **Markdown** content."
}
```
- #### Newline Escaping (Common Pitfall)
-
- In JSON, newline characters inside strings **must** be represented as `\n`. Do **not** use the literal sequence `\\n` in a `.canvas` file—Obsidian will render it as the characters `\` and `n` instead of a line break.
-
- Examples:
-
- ```json
- { "type": "text", "text": "Line 1\nLine 2" }
- ```
+ **Newline pitfall**: Use `\n` for line breaks in JSON strings. Do **not** use the literal `\\n` -- Obsidian renders that as the characters `\` and `n`.
- ```json
- { "type": "text", "text": "Line 1\\nLine 2" }
- ```
+ ### File Nodes
| 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.).
+ | `file` | Yes | string | Path to file within the system |
+ | `subpath` | No | string | Link to heading or block (starts with `#`) |
```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"
- }
- ```
+ ### Link Nodes
| 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.
+ | `url` | Yes | string | External URL |
```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.
+ Groups are visual containers for organizing other nodes. Position child nodes inside the group's bounds.
+ | Attribute | Required | Type | Description |
+ |-----------|----------|------|-------------|
+ | `label` | No | string | Text label for the group |
+ | `background` | No | string | Path to background image |
+ | `backgroundStyle` | No | string | `cover`, `ratio`, or `repeat` |
+
```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.
+ Edges connect nodes via `fromNode` and `toNode` IDs.
- ```json
- {
- "id": "f67890123456789a",
- "fromNode": "6f0ad84f44ce9c17",
- "toNode": "a1b2c3d4e5f67890"
- }
- ```
+ | Attribute | Required | Type | Default | Description |
+ |-----------|----------|------|---------|-------------|
+ | `id` | Yes | string | - | Unique identifier |
+ | `fromNode` | Yes | string | - | Source node ID |
+ | `fromSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
+ | `fromEnd` | No | string | `none` | `none` or `arrow` |
+ | `toNode` | Yes | string | - | Target node ID |
+ | `toSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
+ | `toEnd` | No | string | `arrow` | `none` or `arrow` |
+ | `color` | No | canvasColor | - | Line color |
+ | `label` | No | string | - | Text label |
```json
{
"id": "0123456789abcdef",
"fromNode": "6f0ad84f44ce9c17",
"fromSide": "right",
- "fromEnd": "none",
- "toNode": "b2c3d4e5f6789012",
+ "toNode": "a1b2c3d4e5f67890",
"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"
- }
- ```
+ The `canvasColor` type accepts either a hex string or a preset number:
| 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"
- }
- ]
- }
- ```
+ Preset color values are intentionally undefined -- applications use their own brand colors.
## ID Generation
- Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
+ Generate 16-character lowercase hexadecimal strings (64-bit random value):
- ```json
- "id": "6f0ad84f44ce9c17"
- "id": "a3b2c1d0e9f8g7h6"
- "id": "1234567890abcdef"
```
-
- This format is a 16-character lowercase hex string (64-bit random value).
+ "6f0ad84f44ce9c17"
+ "a3b2c1d0e9f8a7b6"
+ ```
## 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
+ - `x` increases right, `y` increases down; position is the top-left corner
+ - Space nodes 50-100px apart; leave 20-50px padding inside groups
+ - Align to grid (multiples of 10 or 20) for cleaner layouts
| 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
+ ## Validation Checklist
- - 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
+ After creating or editing a canvas file, verify:
- ## Validation Rules
+ 1. All `id` values are unique across both nodes and edges
+ 2. Every `fromNode` and `toNode` references an existing node ID
+ 3. Required fields are present for each node type (`text` for text nodes, `file` for file nodes, `url` for link nodes)
+ 4. `type` is one of: `text`, `file`, `link`, `group`
+ 5. `fromSide`/`toSide` values are one of: `top`, `right`, `bottom`, `left`
+ 6. `fromEnd`/`toEnd` values are one of: `none`, `arrow`
+ 7. Color presets are `"1"` through `"6"` or valid hex (e.g., `"#FF0000"`)
+ 8. JSON is valid and parseable
- 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
+ If validation fails, check for duplicate IDs, dangling edge references, or malformed JSON strings (especially unescaped newlines in text content).
+
+ ## Complete Examples
+
+ See [references/EXAMPLES.md](references/EXAMPLES.md) for full canvas examples including mind maps, project boards, research canvases, and flowcharts.
## References
- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/)
- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)