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):
{
"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" }
]
}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
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
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:
{
"nodes": [{ "id": "web" }, { "id": "api" }, { "id": "db" }],
"edges": [
{ "source": "web", "target": "api" },
{ "source": "api", "target": "db" }
]
}Full example
{
"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/yare 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
brandslug, searchable. - Nodes - the service inspector and metadata.
- Edges - connections in depth.
- Exports - JSON, Markdown, CLAUDE.md and more.