All skills
agricidaniel avatar

/canvas

@b9f5cfe

Create, inspect, and update Obsidian JSON Canvas boards with text, file, link, group, and edge nodes. Use for canvas status, canvas lists, visual maps, zones, spatial layouts, adding vault notes or media to a .canvas file, and requests such as create canvas, add to canvas, or put this on the canvas.

Use this Skill: https://skilld.dev/gh/agricidaniel/claude-obsidian/canvas

This session only. Nothing lands on disk.

referencescanvas-spec.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Obsidian Canvas JSON Specification

Canvas files are JSON with two top-level keys: nodes (array) and edges (array). Obsidian reads and writes them as UTF-8 JSON files with .canvas extension.

This reference aligns with the JSON Canvas 1.0 open specification. All structures support arbitrary additional fields ([key: string]: any) for forward compatibility. Obsidian will preserve unknown fields when reading and writing canvas files.

ID format: JSON Canvas 1.0 requires each node and edge ID to be a unique string; it does not prescribe a length or character set. As a product convention, new mutations from this skill prefer random 16-character lowercase hexadecimal IDs (for example, "a1b2c3d4e5f67890") and verify uniqueness. Descriptive IDs in the longer examples below are readability labels only, not the generation rule.


Coordinate System

        x increases →
   ┌─────────────────────────────────
   │  (-920, -2400)      (0, -2400)
   │
y  │  (-920, 0)          (0, 0) ← origin
↓  │
   │  (-920, 540)        (500, 540)
  • Origin (0, 0) is the center of the canvas viewport.
  • x increases rightward. Negative x = left of center.
  • y increases downward. Negative y = above center.
  • Node x and y are the top-left corner of the node, not the center.
  • Obsidian pans to fit all nodes on first open.

Node Types

Text node

Renders markdown content as a styled card.

{
  "id": "text-title-4821",
  "type": "text",
  "text": "# Heading\n\nParagraph with **bold** and `code`.",
  "x": -400,
  "y": -300,
  "width": 400,
  "height": 120,
  "color": "6"
}
  • text: markdown string. Use \n for newlines.
  • Minimum readable size: width ≥ 200, height ≥ 60.
  • color is optional. Omit for default (no color).

File node

Renders an image, PDF, markdown note, or other vault file inline.

{
  "id": "img-cover-7823",
  "type": "file",
  "file": "_attachments/images/example.png",
  "x": -900,
  "y": -100,
  "width": 420,
  "height": 236
}
  • file: vault-relative path (not absolute, not ~/).
  • Supported: .png .jpg .webp .gif .pdf .md .canvas
  • For .md files: renders as a preview card.
  • For .pdf files: renders the first page as preview.
  • Like every generic node, a file node may include the optional color field. Omit it for the default appearance.

Group node (Zone)

A labeled rectangular region. Does not clip or contain nodes. It's a visual guide. Nodes placed "inside" a group are just positioned within its bounding box.

{
  "id": "zone-branding-3391",
  "type": "group",
  "label": "Brand Identity",
  "x": -920,
  "y": -880,
  "width": 1060,
  "height": 290,
  "color": "6",
  "background": "_attachments/images/grid-bg.png",
  "backgroundStyle": "cover"
}
  • label: shown at the top of the group box.
  • color: colors the group border and label.
  • background (optional): vault-relative path to a background image for the group.
  • backgroundStyle (optional): how the background is rendered.
    • "cover": fills the group, cropping if needed (default-ish behavior)
    • "ratio": preserves aspect ratio, fits inside the group
    • "repeat": tiles the image
  • Groups do not affect auto-layout: they are purely visual containers.

Link node

Renders a web URL as an embedded preview card.

{
  "id": "link-karpathy-2233",
  "type": "link",
  "url": "https://github.com/karpathy",
  "x": 200,
  "y": -300,
  "width": 400,
  "height": 120
}
  • url: must be a valid https:// URL.
  • Obsidian fetches the Open Graph preview (title, description, thumbnail).
  • Writing the node itself performs no fetch. Opening or rendering it in Obsidian may contact the URL's host, so disclose that egress first and use a text node when preview fetching is not acceptable.

Edges

Connections between nodes. Usually empty for mood boards.

{
  "id": "e-hub-cidx",
  "fromNode": "hub",
  "fromSide": "right",
  "fromEnd": "none",
  "toNode": "c-idx",
  "toSide": "left",
  "toEnd": "arrow",
  "label": "concepts",
  "color": "5"
}

Required fields: id, fromNode, toNode. Everything else is optional.

  • fromNode / toNode: IDs of the source and target nodes.
  • fromSide / toSide (optional): "top" "bottom" "left" "right". If omitted, Obsidian auto-calculates the best side based on relative node positions.
  • fromEnd (optional): end-cap on the source side. Defaults to "none". Values: "none" | "arrow".
  • toEnd (optional): end-cap on the target side. Defaults to "arrow": note the asymmetric default vs fromEnd. Values: "none" | "arrow".
  • label (optional): text shown on the edge.
  • color (optional): same color palette as nodes ("1"–"6" or hex).

Most edges represent directed relationships, so the asymmetric defaults (fromEnd: "none", toEnd: "arrow") produce a single arrow pointing from source to target without specifying anything explicitly.


Color Reference

Code Color Hex (approx) Use case
"1" Red / Tomato #e03e3e Warnings, archive
"2" Orange #d09035 Active work
"3" Yellow / Gold #d0a023 WIP, notes
"4" Green / Teal #448361 Content, sources
"5" Blue / Cyan #3ea7d3 Navigation, info
"6" Purple / Violet #9063d2 Title, identity

Omit color entirely for the default (no border color, transparent label).


Image Sizing Guidelines

Calculate from actual image dimensions using PIL or identify:

python3 -c "from PIL import Image; img=Image.open('path.png'); print(img.width, img.height)"
# or
identify -format '%w %h' path.png
Aspect ratio Condition Canvas width Canvas height
16:9 (wide) ratio 1.6–2.0 420 236
2:1 (ultra wide) ratio > 2.0 440 220
4:3 ratio 1.2–1.6 380 285
1:1 (square) ratio 0.9–1.1 280 280
3:4 ratio 0.6–0.9 240 320
9:16 (portrait) ratio < 0.6 200 356
PDF any 400 520
Unknown fallback 320 240

Auto-Positioning Pseudocode

function place_node(canvas, zone_label, new_w, new_h):
  zone = find group node where label == zone_label
  padding = 20

  if zone not found:
    max_y = max(n.y + n.height for n in canvas.nodes) + 60
    return (-400, max_y)

  # Nodes visually inside zone
  inside = [n for n in canvas.nodes
            if n.type != 'group'
            and zone.x <= n.x < zone.x + zone.width
            and zone.y <= n.y < zone.y + zone.height]

  if inside is empty:
    return (zone.x + padding, zone.y + padding)

  # Rightmost point in zone
  rightmost = max(n.x + n.width for n in inside)
  next_x = rightmost + 40

  if next_x + new_w > zone.x + zone.width - padding:
    # Overflow → new row
    bottom_of_row = max(n.y + n.height for n in inside)
    return (zone.x + padding, bottom_of_row + padding)

  # Same row
  row_y = min(n.y for n in inside)  # align to top of existing row
  return (next_x, row_y)

Full Example: Two-Zone Canvas

{
  "nodes": [
    {
      "id": "title-0001",
      "type": "text",
      "text": "# Brand Reference\n\n**AI Marketing Hub** visual assets",
      "x": -920, "y": -2440, "width": 560, "height": 180, "color": "6"
    },
    {
      "id": "zone-logos",
      "type": "group",
      "label": "Logos & Icons",
      "x": -920, "y": -2200, "width": 1800, "height": 320, "color": "6"
    },
    {
      "id": "img-logo-pro",
      "type": "file",
      "file": "_attachments/images/example.png",
      "x": -900, "y": -2180, "width": 420, "height": 236
    },
    {
      "id": "img-icon-free",
      "type": "file",
      "file": "_attachments/images/example-icon.png",
      "x": -440, "y": -2180, "width": 280, "height": 280
    },
    {
      "id": "zone-covers",
      "type": "group",
      "label": "Skill Covers",
      "x": -920, "y": -1820, "width": 1800, "height": 340, "color": "3"
    },
    {
      "id": "img-seo",
      "type": "file",
      "file": "_attachments/images/example-cover.png",
      "x": -900, "y": -1800, "width": 420, "height": 236
    }
  ],
  "edges": []
}

Common Mistakes

  • Wrong path format: use _attachments/images/file.png, not an absolute host path
  • ID collision: always read existing IDs before generating a new one
  • Negative y confusion: y: -2400 is ABOVE y: -1000 (more negative = higher up)
  • Group does not clip: placing a node "inside" a group is just positioning it within the group's bounding box: there is no parent-child relationship in the JSON
  • Missing height on text nodes: Obsidian will render the text but may clip it if height is too small. Use height ≥ content-lines × 24.

Source: SKILL.md on GitHub

No alerts17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill allows the agent to create and modify Obsidian Canvas files. It uses a local transaction script and standard image processing utilities to manage file changes safely within a user's vault. It includes specific mitigations against path traversal and ensures a manual approval step for all vault modifications.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at b9f5cfe. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 3 weeks ago

README badge

README badge for agricidaniel/claude-obsidian/canvas