Skip to content

Data Format

TopoKit reads a single JSON config describing your graph and how to render it. This page documents every field. The same JSON works in the SDK, in TopoKit Play, and in the HTTP API (POST /api/topos).

Top-level shape

Two arrays carry the graph (nodes, edges). Every other field is optional and configures presentation: layout, theme, built-in UI components, and a few rendering knobs.

JSON
{
  "nodes": [
    { "id": "a", "data": {}, "style": { "label": { "text": "A" } } },
    { "id": "b", "data": {}, "style": { "label": { "text": "B" } } },
    { "id": "c", "data": {}, "style": { "label": { "text": "C" } } }
  ],
  "edges": [
    { "id": "e1", "source": "a", "target": "b", "data": {} },
    { "id": "e2", "source": "b", "target": "c", "data": {} }
  ],
  "layout": "force",
  "theme": "dark"
}
FieldTypeDefaultDescription
nodesNode[][]Graph nodes. See Nodes.
edgesEdge[][]Graph edges. See Edges.
layoutstring'force'Layout algorithm id. See Layouts.
themestring'dark'Visual theme id. See Themes.
directedbooleantrueWhether edges have direction (draws arrowheads).
allowSelfLoopsbooleanfalsePermit edges where source === target.
allowParallelEdgesbooleanfalsePermit multiple edges between the same pair.
fitPaddingnumber80Pixels of padding around the graph after fit-to-screen.
autoFitOnResizebooleanfalseRe-fit when the container resizes.
searchboolean | objecttrueSearch bar. See UI components.
minimapboolean | objecttrueBird-eye minimap.
toolbarboolean | objecttrueZoom controls / fit-to-screen.
tooltipboolean | objecttrueHover popup.
contextMenuboolean | objecttrueRight-click menu.
layoutSelectorboolean | objecttrueLayout dropdown.
themeSelectorboolean | objecttrueTheme dropdown.
infoPanelboolean | objecttrueSide panel showing selected node details.
hoverFocusboolean | objecttrueDim unrelated nodes on hover.
legendboolean | objectfalseColor/shape key. Opt-in (set truthy to show).
edgeBundlingboolean | 'fdeb' | 'merge' | objectfalseEdge bundling. true auto-pivots fdeb→merge at ≥150 edges. See Edge Bundling.
autoFitOnChangebooleantrueRe-fit viewport on batch:end / focus:*. Suppressed during manual pan/zoom.
hideOrphansbooleanfalseHide non-combo nodes whose visible incident edge count is zero.
autoCollapseCombosForcebooleanfalseBypass the “user already collapsed something” guard for force layouts.
comboEdgeAggregationbooleantrueSynthesize cross-boundary combo edges. Set false to drop them.

All booleans above are subject to the “defaults scrubbed” rule: toggling a key away from its default writes the explicit value; toggling back to the default removes the leaf from the saved JSON. This keeps the canonical form minimal — if a field isn't listed in your saved JSON, it's running with the default shown above.

Any UI-component field accepts false to disable, true to enable with defaults, or an options object for per-component tuning. legend is the only one that is off by default.

nodes

Each node is an object. Only id and data are required.

FieldTypeRequiredDescription
idstringyesUnique identifier.
dataobjectyesArbitrary user data. Pass if you have none — used by tooltips, info panel, and any custom code.
position{ x, y }noInitial coordinates. Layout overwrites these unless the node is locked.
styleNodeStylenoVisual properties. See below.
visiblebooleannoHide without removing.
lockedbooleannoLayout will not move this node.
parentstringnoParent node id, for combo / group nodes.
collapsedbooleannoIf this is a combo, render it collapsed.

NodeStyle

FieldTypeDefaultDescription
shape'circle' | 'rect' | 'rounded-rect' | 'diamond' | 'hexagon''circle'See Node shapes.
radiusnumbertheme default (~20)For circle, the radius. For other shapes, the half-extent.
widthnumberderived from radiusExplicit width for rect-family shapes.
heightnumberderived from radiusExplicit height for rect-family shapes.
fillstring (CSS color)theme defaultFill color.
strokestringtheme defaultBorder color.
strokeWidthnumber2Border width in px.
opacitynumber (0–1)1Whole-node opacity.
imagestring (URL)Optional raster image rendered inside the node.
imageWidthnumberautoImage width in px.
imageHeightnumberautoImage height in px.
imageRadiusnumberClip image with a circle of this radius.
labelLabelStyleText label config (see below).
badgesNodeBadge[][]Small badges anchored to corners (see below).

label (on NodeStyle)

FieldTypeDefaultDescription
textstringThe visible label text. If omitted, the node renders unlabeled.
colorstringtheme defaultText color.
fontSizenumber12Pixel size.
fontFamilystringtheme defaultCSS font-family.
position'center' | 'top' | 'bottom' | 'left' | 'right'theme defaultWhere the label sits relative to the node.
visiblebooleantrueToggle without removing text.

NodeBadge

FieldTypeDefaultDescription
textstringBadge text. Pair with icon or use alone.
icon'dot' | 'alert' | 'check' | 'star'Built-in icon to draw inside the badge.
shape'circle' | 'rect''circle'Badge shape.
position'top-right' | 'top-left' | 'bottom-right' | 'bottom-left''top-right'Anchor corner.
colorstringtheme defaultForeground (text / icon) color.
backgroundstringtheme defaultFill color.
visiblebooleantrueToggle.

edges

Edges are always objects in TopoKit — there is no [src, tgt] tuple short-form. Each edge needs an id so TopoKit can address it individually, plus source, target, and a data object (pass if none).

FieldTypeRequiredDescription
idstringyesUnique identifier. "e1", "a-b", anything stable.
sourcestringyesSource node id (must exist in nodes).
targetstringyesTarget node id (must exist in nodes).
dataobjectyesArbitrary user data. Use if none.
styleEdgeStylenoVisual properties (see below).
visiblebooleannoHide without removing.

EdgeStyle

FieldTypeDefaultDescription
type'straight' | 'bezier' | 'step''straight'Path style. See Edge types.
strokestringtheme defaultLine color.
strokeWidthnumber1.5Line width in px.
opacitynumber (0–1)0.8Line opacity.
arrowbooleanfollows top-level directedDraw an arrowhead at the target end.
arrowSizenumber8Arrowhead size in px.
dashedbooleanfalseDashed line.
dashPatternnumber[]Custom dash pattern, e.g. [4, 4].
animatedbooleanfalseAnimate dashes (flowing edge effect).
animationSpeednumber1Multiplier for the dash animation.
labelEdgeLabelStyleMidpoint label (see below).

label (on EdgeStyle)

FieldTypeDefaultDescription
textstringLabel text rendered at the edge midpoint.
colorstringtheme defaultText color.
backgroundstringtheme defaultBacking fill (helps readability over the line).
fontSizenumber10Pixel size.
fontFamilystringtheme defaultCSS font-family.
visiblebooleanfalseEdge labels are off by default.

layout

String id of a layout algorithm. The renderer runs it on load and re-runs on demand.

IdNameWhen to use
forceForce-directedGeneral-purpose. Best default for unstructured networks.
combo-forceCombo-forceForce plus per-combo centroid spring and inter-combo repulsion. Use when nodes have parent set.
hierarchicalHierarchicalOrg charts, dependency trees, workflows — layered top-down or left-right.
dagreDagreHierarchical with edge-crossing minimization. Good for DAGs with crossings.
radialRadialTree rooted at a node, drawn as concentric rings.
circularCircularAll nodes evenly spaced on one circle. Highlights connectivity patterns.
concentricConcentricGroup nodes onto concentric rings by a metric (degree, weight, etc.).
gridGridUniform grid placement. Useful for small graphs and screenshots.
autoAutoPick the best layout from graph-shape heuristics. Good when you don't know.

theme

IdBest for
lightDocumentation, print, embeds on light pages.
darkDev tools, dashboards (default).
corporateBusiness presentations, slide decks.
neonMonitoring screens, telemetry walls.
pastelEducational, soft visuals.
monochromePrint, academic figures.
oceanData dashboards with cool palette.

Each theme provides default fill, stroke, label color, and edge color. Anything you set explicitly on NodeStyle / EdgeStyle overrides the theme. Custom themes (defining your own palette) are supported through the SDK API — not through the JSON config — see Core Concepts → Themes.

Node shapes

IdNameNotes
circleCircleDefault. Uses radius.
rectRectangleSharp corners. Uses width/height or radius.
rounded-rectRounded rectangleSame as rect but with rounded corners.
diamondDiamond45°-rotated square.
hexagonHexagonRegular six-sided polygon.

Edge types

IdNameNotes
straightStraightDefault. Direct line between endpoints.
bezierBezierSmooth cubic curve. Looks good with force layouts.
stepStepRight-angle orthogonal routing. Looks good with hierarchical / dagre.

UI components

Each component is configured by a top-level key. Pass true / false to toggle, or an object to tune options. All except legend are on by default.

FieldWhat it showsDefault
searchTop-center search bar; filters/focuses nodes by label or id.on
minimapBottom-left bird-eye overview with viewport rectangle.on
toolbarBottom-right zoom controls and fit-to-screen button.on
tooltipHover popup with node id and selected data fields.on
contextMenuRight-click menu (Select neighbors / Hide by default).on
layoutSelectorTop-left dropdown to switch layout algorithm at runtime.on*
themeSelectorTop-right dropdown to switch theme at runtime.on
infoPanelSlide-in side panel showing the selected node's full data.on
hoverFocusDim unrelated nodes/edges when hovering one.on
legendColor/shape key auto-derived from node and edge types.off

* layoutSelector only renders if the host page supplies a layout runner (Play and the SDK quick-start both do).

Worked examples

Minimal — three nodes

JSON
{
  "nodes": [
    { "id": "a", "data": {}, "style": { "label": { "text": "A" } } },
    { "id": "b", "data": {}, "style": { "label": { "text": "B" } } },
    { "id": "c", "data": {}, "style": { "label": { "text": "C" } } }
  ],
  "edges": [
    { "id": "e1", "source": "a", "target": "b", "data": {} },
    { "id": "e2", "source": "b", "target": "c", "data": {} }
  ],
  "layout": "force",
  "theme": "dark"
}

Org chart — dagre + light theme

JSON
{
  "nodes": [
    { "id": "ceo", "data": { "role": "CEO" }, "style": { "shape": "rounded-rect", "fill": "#3B82F6", "radius": 28, "label": { "text": "CEO", "color": "#fff" } } },
    { "id": "cto", "data": { "role": "CTO" }, "style": { "shape": "rounded-rect", "fill": "#10B981", "radius": 24, "label": { "text": "CTO", "color": "#fff" } } },
    { "id": "cfo", "data": { "role": "CFO" }, "style": { "shape": "rounded-rect", "fill": "#10B981", "radius": 24, "label": { "text": "CFO", "color": "#fff" } } },
    { "id": "eng-vp", "data": { "role": "VP Eng" }, "style": { "fill": "#8B5CF6", "label": { "text": "VP Eng", "color": "#fff" } } },
    { "id": "data-vp", "data": { "role": "VP Data" }, "style": { "fill": "#8B5CF6", "label": { "text": "VP Data", "color": "#fff" } } },
    { "id": "fin-mgr", "data": { "role": "Finance" }, "style": { "fill": "#F59E0B", "label": { "text": "Finance", "color": "#fff" } } },
    { "id": "eng-1", "data": { "role": "Backend" }, "style": { "fill": "#EC4899", "label": { "text": "Backend", "color": "#fff" } } },
    { "id": "eng-2", "data": { "role": "Frontend" }, "style": { "fill": "#EC4899", "label": { "text": "Frontend", "color": "#fff" } } },
    { "id": "data-1", "data": { "role": "Data Eng" }, "style": { "fill": "#06B6D4", "label": { "text": "Data Eng", "color": "#fff" } } },
    { "id": "data-2", "data": { "role": "ML" }, "style": { "fill": "#06B6D4", "label": { "text": "ML Eng", "color": "#fff" } } }
  ],
  "edges": [
    { "id": "e1", "source": "ceo", "target": "cto", "data": {} },
    { "id": "e2", "source": "ceo", "target": "cfo", "data": {} },
    { "id": "e3", "source": "cto", "target": "eng-vp", "data": {} },
    { "id": "e4", "source": "cto", "target": "data-vp", "data": {} },
    { "id": "e5", "source": "cfo", "target": "fin-mgr", "data": {} },
    { "id": "e6", "source": "eng-vp", "target": "eng-1", "data": {} },
    { "id": "e7", "source": "eng-vp", "target": "eng-2", "data": {} },
    { "id": "e8", "source": "data-vp", "target": "data-1", "data": {} },
    { "id": "e9", "source": "data-vp", "target": "data-2", "data": {} }
  ],
  "layout": "dagre",
  "theme": "light",
  "minimap": true,
  "toolbar": true
}

Rich — telecom network, all the trimmings

JSON
{
  "nodes": [
    { "id": "core-1", "data": { "type": "core" }, "style": { "shape": "hexagon", "fill": "#7C3AED", "radius": 26, "label": { "text": "Core-A", "color": "#fff" }, "badges": [{ "icon": "alert", "background": "#EF4444", "position": "top-right" }] } },
    { "id": "core-2", "data": { "type": "core" }, "style": { "shape": "hexagon", "fill": "#7C3AED", "radius": 26, "label": { "text": "Core-B", "color": "#fff" } } },
    { "id": "edge-1", "data": { "type": "edge" }, "style": { "shape": "rect", "fill": "#0EA5E9", "label": { "text": "Edge-1", "color": "#fff" } } },
    { "id": "edge-2", "data": { "type": "edge" }, "style": { "shape": "rect", "fill": "#0EA5E9", "label": { "text": "Edge-2", "color": "#fff" } } },
    { "id": "edge-3", "data": { "type": "edge" }, "style": { "shape": "rect", "fill": "#0EA5E9", "label": { "text": "Edge-3", "color": "#fff" } } },
    { "id": "user-1", "data": { "type": "user" }, "style": { "fill": "#22C55E", "label": { "text": "User-1", "color": "#fff" } } },
    { "id": "user-2", "data": { "type": "user" }, "style": { "fill": "#22C55E", "label": { "text": "User-2", "color": "#fff" } } }
  ],
  "edges": [
    { "id": "c1c2", "source": "core-1", "target": "core-2", "data": { "bw": "100G" }, "style": { "type": "bezier", "stroke": "#A78BFA", "strokeWidth": 3, "label": { "text": "100G" } } },
    { "id": "c1e1", "source": "core-1", "target": "edge-1", "data": {}, "style": { "type": "bezier" } },
    { "id": "c1e2", "source": "core-1", "target": "edge-2", "data": {}, "style": { "type": "bezier" } },
    { "id": "c2e3", "source": "core-2", "target": "edge-3", "data": {}, "style": { "type": "bezier" } },
    { "id": "e1u1", "source": "edge-1", "target": "user-1", "data": {}, "style": { "dashed": true } },
    { "id": "e2u2", "source": "edge-2", "target": "user-2", "data": {}, "style": { "dashed": true } }
  ],
  "layout": "force",
  "theme": "neon",
  "directed": true,
  "search": true,
  "minimap": true,
  "toolbar": true,
  "tooltip": true,
  "layoutSelector": true,
  "themeSelector": true,
  "legend": { "position": "bottom-left" },
  "infoPanel": true,
  "hoverFocus": true,
  "fitPadding": 80
}

Versioning & evolution

TopoKit's JSON config is a living schema. New optional fields may be added between SDK releases. Old fields remain backward-compatible.

  • TopoKit Play (/play) and shared topos (/t/<hash>) are dumb pipes. They pass your JSON straight to the SDK without validation — any field the current SDK accepts will work.
  • Unknown fields are silently ignored. Setting a field the current SDK doesn't recognize will not error; it just has no effect.
  • This page is the contract surface. When the SDK gains new options, this page is the one that needs updating — not /play or the share routes.
  • Runtime introspection. The SDK ships an authoritative manifest of layouts, themes, UI components, node shapes, and edge types: import { OPTIONS } from '@topokit/core' or window.GraphKit.options in the IIFE bundle.

Tips for AI agents

If you are an LLM or automation pipeline converting user data to TopoKit JSON, these heuristics work well.

  • Start with the minimum. Produce a valid nodes + edges blob first; layer in optional fields after.
  • Always include data: {} on every node and edge — it is required, even if empty.
  • Default layout: force for unknown graph shapes. Switch to dagre for clear parent-child structure; radial or hierarchical for explicit trees.
  • Default theme: dark for technical / network graphs, light for org charts and document-like trees.
  • Adaptive UI: enable search when nodes > 30; enable minimap when nodes > 50; enable legend when you set more than one distinct fill color.
  • Don't invent fields. Stick to the names in the tables above. Unknown keys are ignored, so they will not error — but they will not do anything either.
  • Posting to the API: POST https://topokit.io/api/topos with the JSON as body returns {"hash":"<short>"}. View at https://topokit.io/t/<hash>.

The TopoKit Play “Use with AI” modal contains ready-to-copy prompts for the two most common workflows (paste-and-tweak vs. agent-does-everything).

Next