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 technology - stripe, next - and pick
the brand mark), billing models, edge line types, and the node ids
an edge can point to. Press Ctrl Space 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": "My Stack",
"nodes": [
{ "id": "web", "label": "Web App", "brand": "nextdotjs", "spend": "$1.2k" },
{ "id": "api", "label": "API", "brand": "nodedotjs", "spend": "$2.1k" }
],
"edges": [{ "source": "web", "target": "api" }]
}Only id is required on a node; source and target are required on an edge.
Every other field is optional.
Canopy also stores an optional canvas object with your per-map view settings
(background, edgeStyle, minimap, snapToGrid). It's written automatically
when you save and restored on load - you don't need to author it by hand, and
older files without it just fall back to the defaults.
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",
"nodes": [
{
"id": "web",
"label": "Web App",
"tech": "next.js",
"brand": "nextdotjs",
"purpose": "Frontend application",
"spend": "$1.2k",
"billing": ["flat"],
"x": 0,
"y": 96
},
{
"id": "api",
"label": "API",
"tech": "node · trpc",
"brand": "nodedotjs",
"purpose": "Backend REST API",
"spend": "$2.1k",
"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": "$2.8k",
"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 icon library - search every brand, or pick a generic service box.
- The inspector edits a node's label, tech sub-label, icon (
brand), monthly spend, 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.