Map syntax (architecture as code)

Map syntax

Every Canopy map is plain JSON - easy to read, diff, and edit by hand or with an agent. This page is the complete syntax / schema reference: every field, its type, and its accepted values. (Canopy calls this "architecture as code".) Open any map and switch the editor to Split or Code to see it; edit the canvas or edit the JSON and they stay in sync.

Plain JSON means your maps are portable - version them in git, generate them from a script, or read and write them through the API and MCP server.

The Code editor has autocomplete (IntelliSense). As you type, it suggests field names, icon slugs (type a tool or step - stripe, next, approval - and pick the mark), billing models, edge line types, and the node ids an edge can point to. Press . (Ctrl . on Windows/Linux) to re-open suggestions, to move, Enter or Tab to accept.

Shape

A map is an object with two arrays, nodes and edges (an optional name is also stored):

JSON
{
  "name": "AI support workflow",
  "canvas": { "currency": "USD" },
  "nodes": [
    { "id": "inbox", "label": "Inbox", "brand": "intercom", "spend": 199 },
    {
      "id": "agent",
      "label": "Triage agent",
      "brand": "openai",
      "spend": 840,
      "currency": "EUR"
    },
    {
      "id": "review",
      "label": "Human review",
      "brand": "generic-approval",
      "spend": 0
    }
  ],
  "edges": [
    { "source": "inbox", "target": "agent" },
    { "source": "agent", "target": "review" }
  ]
}
the same JSON, rendered on the canvas

Only id is required on a node; source and target are required on an edge. Every other field is optional.

Canopy also stores a canvas object with your per-map view settings (background, edgeStyle, minimap, snapToGrid) plus the map's currency. The view settings are written automatically when you save and restored on load, so you don't need to author them by hand, and older files without them fall back to the defaults. currency is the one required canvas field: the map's base currency as an ISO 4217 code (one of USD, EUR, GBP, JPY, CNY, CAD, AUD, CHF, INR, SGD). It defaults to the workspace currency, and any node without its own currency override uses it.

Node fields

FieldTypeRequiredAccepted valuesNotes
idstringyesLetters, digits, -, _ only - /^[A-Za-z0-9_-]+$/. Unique within the map.The node's stable key; edges reference it.
labelstringAny text (kept ≤ 80 chars).Display name on the canvas. Defaults to id when omitted.
techstringAny text (kept ≤ 60 chars).Tool, runtime, role, or step under the name, e.g. "node · trpc" or "approval". Also accepted as sub.
brandstringAn icon slug from the Icon library, e.g. "postgresql", "openai".Brand icon. Also accepted as icon. An unknown slug → a neutral box node.
purposestringAny text (kept ≤ 160 chars).What the service does; shown in the inspector and in CLAUDE.md/AGENTS.md exports.
spendnumberA plain number, e.g. 840. No symbol.Monthly cost as a plain number in the map's currency, e.g. 840. No symbol. Defaults to 0.
currencystringOne of "USD", "EUR", "GBP", "JPY", "CNY", "CAD", "AUD", "CHF", "INR", "SGD".Optional ISO 4217 code overriding the map's canvas.currency for this node's spend.
billingstring[]Any of "seat", "team", "usage", "flat". Unknown values are dropped.Billing-model tags shown on the node and rolled up on the Intel page.
x, ynumberAny number (pixels).Canvas position. Auto-assigned when omitted - see Auto-layout.

Not a node field

There is no per-node color. The map has a single accent colour set from the editor; individual nodes take their hue from their brand icon.

Edge fields

FieldTypeRequiredAccepted valuesNotes
sourcestringyesAn existing node id.The edge's start. (This is source, not from.)
targetstringyesAn existing node id.The edge's end. (This is target, not to.)
linestringOne of "bezier", "smoothstep", "step", "straight".Edge curve style. Defaults to "bezier"; an invalid value falls back to "bezier".
labelstringAny short string (max 120 chars).Text drawn at the edge midpoint. Omit for no label.
labelColorstringA CSS color: hex, rgb()/hsl(), or a named color.Label text color. Defaults to a muted foreground.
labelSizenumberInteger 6-48.Label font size in px. Defaults to 10.
labelFontstringOne of "sketch", "sans", "mono".Label typeface. "sketch" (Excalifont) is the default; all are open-source (SIL OFL).
arrowstringOne of "none", "end", "both".Arrowheads / direction. "end" = single arrow at the target, "both" = bidirectional. Defaults to "none".
colorstringA CSS color: hex ("#38bdf8"), rgb()/hsl(), or a named color.Overrides the default gradient stroke. Invalid values are ignored.
dotColorstringA CSS color: hex, rgb()/hsl(), or a named color.Color of the moving flow dot. Defaults to the edge color (or the gradient accent).
animatedbooleantrue or false.The moving-dot flow animation. Defaults to true; set false for a static line.
dashedbooleantrue or false.Dashed stroke instead of solid. Defaults to false.
idstringAny string.Optional stable edge id; auto-generated when omitted.

Both ends must reference a node id that exists in the same nodes array - edges with a missing or unknown source/target are silently dropped.

Auto-layout

When x/y are omitted on any node, Canopy assigns positions automatically using a left-to-right layered layout: each depth level from the edge graph gets its own column (spaced 240 px apart), and nodes in the same column are evenly distributed vertically (96 px row height). Once you drag a node, its position is written back to the JSON and no longer auto-managed.

Minimal map

Only id (nodes) and source/target (edges) are required:

JSON
{
  "nodes": [{ "id": "web" }, { "id": "api" }, { "id": "db" }],
  "edges": [
    { "source": "web", "target": "api" },
    { "source": "api", "target": "db" }
  ]
}

Full example

JSON
{
  "name": "AI SaaS",
  "canvas": { "currency": "USD" },
  "nodes": [
    {
      "id": "web",
      "label": "Web App",
      "tech": "next.js",
      "brand": "nextdotjs",
      "purpose": "Frontend application",
      "spend": 1200,
      "billing": ["flat"],
      "x": 0,
      "y": 96
    },
    {
      "id": "api",
      "label": "API",
      "tech": "node · trpc",
      "brand": "nodedotjs",
      "purpose": "Backend REST API",
      "spend": 2100,
      "billing": ["flat"],
      "x": 240,
      "y": 144
    },
    {
      "id": "db",
      "label": "Postgres",
      "brand": "postgresql",
      "purpose": "Primary database",
      "spend": 840,
      "billing": ["usage", "team"],
      "x": 480,
      "y": 48
    },
    {
      "id": "llm",
      "label": "LLM",
      "brand": "openai",
      "purpose": "AI completions",
      "spend": 2800,
      "billing": ["usage"],
      "x": 240,
      "y": 240
    }
  ],
  "edges": [
    { "source": "web", "target": "api" },
    { "source": "api", "target": "db", "line": "step" },
    { "source": "api", "target": "llm" }
  ]
}

Editing

  • Add node opens the tool and icon library - search every brand, workflow platform, AI tool, or pick a generic service or workflow box.
  • The inspector edits a node's label, tool/step sub-label, icon (brand), monthly cost, purpose, and billing tags.
  • Drag between handles to connect; drag a node to move it - its x / y are written back to the JSON.
  • A map colour swatch sets the map's accent, reflected on the dashboard overview.
  • If the JSON is invalid, the offending line is flagged and the canvas keeps your last valid render until you fix it.

Importing

Already have a map written to this syntax? You can paste it straight in via New map → Paste canopy.json - see Import from canopy.json for the full flow and what gets validated.

Next

  • Icon library - every brand slug, searchable.
  • Nodes - the service inspector and metadata.
  • Edges - connections in depth.
  • Exports - JSON, Markdown, CLAUDE.md and more.

to paste into any AI.

Ask ChatGPT about this page.

Ask Claude about this page.

Need help? Sign in to chat with support

Have ideas?

General inquiry? Email support.