---
name: flowdiagram
description: Generate FlowDiagram diagrams (flowcharts, system/integration landscapes, ER models) and deliver both a .flow.json file and a one-click flowdiagram.net share link that opens it. Use when asked to diagram an architecture, data flow, integration landscape, or database schema.
---

# FlowDiagram diagram generation

## Deliver two things, every time

1. **A `.flow.json` file on disk.** It opens in FlowDiagram (a single-file HTML
   diagram editor) via **☰ → Open .flow.json**. Tell the user the path.
2. **A share link to that same diagram** — `https://flowdiagram.net/#d=z…`. The
   document is encoded into the URL itself, so one click opens the finished
   diagram with nothing to download and nothing to install.

Both, on every diagram request, unless the user asked for only one. §11 is the
recipe for the link: one snippet run in your built-in browser, a couple of
seconds. Do not stop at the file — a path in a chat reply is something the user
still has to go and act on, whereas the link *is* the diagram.

Do not print large JSON into chat. The file holds it and the link carries it.

If the user only needs a quick flowchart and doesn't mention FlowDiagram, plain
Mermaid is fine — FlowDiagram imports Mermaid too (**☰ → Import diagram**). Use
`.flow.json` when you need system logos, ER tables, comments, zones, or lineage
links, none of which Mermaid can express.

---

## 1. Two rules that break diagrams if ignored

Both are non-obvious because FlowDiagram's *internal* default differs from the
fallback applied to externally-authored JSON. What the app uses when *you* draw
a shape is not what it falls back to when it reads your file:

1. **Always set `"fontFamily": "sans"` on every element that has text.** If
   omitted it defaults to `"hand"`, a handwriting font.

2. **Always set `"routing": "elbow"` on every arrow.** If omitted it falls
   back to `"straight"` — and a straight connector does no obstacle avoidance
   whatsoever. It draws a direct line from A to B, straight through anything in
   between. Only `"elbow"` detours around other shapes.

Omitting anything else is safe. Omitting either of these produces a diagram
that looks wrong and the user has to fix by hand.

`"roughness"` is still a field in the schema, but it no longer changes
anything: FlowDiagram always draws (and exports) clean, precise lines now —
sketchiness stopped being a per-diagram option, so there is no "hand-drawn"
look left to accidentally trigger. Keep writing `"roughness": 0` anyway for
files that might be opened by something other than the app itself, or by an
older version of it, but do not worry if a template you're copying from
omits it.

---

## 2. File shape

Only `pages` is required. Everything else is optional metadata.

```json
{
  "app": "FlowDiagram",
  "version": 1,
  "savedAt": "2026-01-01T00:00:00.000Z",
  "pages": [
    { "id": "p1", "name": "Architecture", "els": [] }
  ]
}
```

- `els` is a flat, ordered array — later elements paint on top.
- Every element needs a **unique** `id` (any string). Arrows reference shapes by `id`.
- Multiple pages are allowed; each is a separate tab in the app.

---

## 3. Common fields

Every element carries these. Copy this block and adjust:

```json
{
  "id": "n1", "type": "rect",
  "x": 0, "y": 0, "w": 150, "h": 80,
  "angle": 0, "seed": 1001,
  "stroke": "#4dabf7", "fill": "transparent", "fillStyle": "solid",
  "strokeWidth": 2, "strokeStyle": "solid",
  "roughness": 0, "opacity": 1, "roundness": 1,
  "text": "Label", "fontSize": 18, "fontFamily": "sans",
  "textAlign": "center", "textColor": null,
  "locked": false, "group": null
}
```

| Field | Values |
|---|---|
| `strokeStyle` | `"solid"` · `"dashed"` · `"dotted"` |
| `fillStyle` | `"solid"` · `"hachure"` · `"cross"` |
| `fill` | `"transparent"` or any hex |
| `roundness` | `1` rounded corners · `0` sharp |
| `layer` | id of the page layer this element belongs to (see Layers below) |
| `fontFamily` | `"sans"` · `"mono"` · `"serif"` · `"hand"` |
| `textAlign` | `"left"` · `"center"` · `"right"` |

**Default palette:** stroke `#4dabf7`, relationship lines `#5c6b84`,
lineage links `#0c8599`. Light fills that read well: `#d0ebff` `#d3f9d8`
`#ffec99` `#ffd8a8` `#e9ecef`.

---

## 4. Element types

### Shapes
`rect` · `ellipse` · `diamond` · `cylinder` · `hexagon` · `parallelogram` ·
`triangle` · `trapezoid` · `star` · `cloud` · `cross` · `document` · `process`

Use the common block above. `cylinder` = datastore, `diamond` = decision,
`parallelogram` = data in/out. Suggested size 150×80 (diamond 150×110).

### Standard flowchart symbols
`terminator` · `manualinput` · `manualop` · `delay` · `storeddata` ·
`display` · `multidoc` · `offpage` · `merge` · `junction`

The rest of the classic flowchart vocabulary, for when a diagram is a real
process flow rather than a system landscape:

| type | meaning |
|---|---|
| `terminator` | start / end (stadium) |
| `manualinput` | data keyed in by a person (sloped top) |
| `manualop` | a step done by hand (trapezoid, wide at top) |
| `delay` | a wait or queue |
| `storeddata` | stored data, generic |
| `display` | shown to a person: screen, report |
| `multidoc` | several documents |
| `offpage` | continues on another page or diagram |
| `merge` | two paths merging (inverted triangle) |
| `junction` | summing junction |

Use `terminator` for the first and last node of a process flow — a `rect`
start box is the single most common thing that makes a generated flowchart
look wrong. Suggested size 150×80 (`terminator` 140×60, `junction` 70×70).

### Process notation (BPMN / UML activity)
`lane` · `forkbar` · `event` · `onpage` · `annotation` · `looplimit`

For a business process rather than a system landscape — especially one that
crosses teams.

| type | meaning |
|---|---|
| `lane` | swimlane. Title runs sideways in a strip down its left edge; everything sitting inside travels with it when it moves. Stack them to build a pool. Suggested size 900×180 |
| `forkbar` | the solid bar that splits flow into parallel branches, and the one that joins them again. Suggested size 180×10 (or 10×180 upright) |
| `event` | BPMN event circle. `evt` picks which: `start` (thin ring), `inter` (double ring), `end` (thick ring). Suggested size 56×56 |
| `onpage` | on-page connector — a small labelled circle, usually one letter. Suggested size 44×44 |
| `annotation` | a bracket with a note beside it; comments on a step without being one. Suggested size 170×80 |
| `looplimit` | marks the start or end of a loop. Suggested size 150×80 |

Two ordinary shapes carry a process marker instead of changing outline:

- **`diamond` + `gw`** — `x` exclusive (one path only), `plus` parallel
  (every path), `o` inclusive (one or more). A plain diamond cannot say which
  it means, so set `gw` whenever the branch matters.
- **`rect` + `mark`** — `sub` sub-process, `user` human task, `service`
  automated call, `script` script task.

Put a lane behind the steps it owns, not around them: a lane is a band, and
the steps are ordinary shapes positioned inside its bounds.

### 3-D solids
`cube` · `tube` · `sphere` · `cone` · `pyramid`

Shaded solids, lit from the upper left. Use them for volume/capacity pictures,
storage tiers, stacked environments — not for ordinary process boxes, which
should stay flat. Suggested size 150×140.

```json
{ "id":"v1", "type":"cube", "x":0, "y":0, "w":150, "h":140,
  "fill":"#4dabf7", "stroke":"#1e1e1e", "roughness":0, "text":"Raw zone" }
```

Give a solid a real `fill` — that colour is what the faces are shaded from.
A `"transparent"` fill still renders, as a pale wash of `stroke`, but a chosen
colour reads far better. Optional `depth` (number, roughly 2–90) sets how far
the back of the solid is pushed up and to the right; omit it for an automatic
depth derived from the size.

`material` — `"glass"` (default) or `"solid"`. Glass is translucent with a
glossy highlight and its hidden edges sketched back in as faint dashed lines,
so whatever sits behind or inside it still shows through. `solid` is a flat
opaque shaded surface. Omit the field to get glass.

### Fill patterns
Any element with a real `fill` may add `fillPattern`, one of
`"dots"` · `"grid"` · `"lines"` · `"cross"` · `"checker"`.
Omit it (or use `null`) for a plain fill. On a 3-D solid the pattern is tinted
per face, so the shading still reads through it.

### `node` — a system with a logo
```json
{ "id":"s1", "type":"node", "icon":"snowflake", "text":"Snowflake",
  "x":0, "y":0, "w":130, "h":104,
  "stroke":"#4dabf7", "fill":"transparent", "roundness":1,
  "roughness":0, "fontSize":13, "fontFamily":"sans", "textAlign":"center",
  "opacity":1, "seed":1002 }
```
`icon` must be a key from §7. An unknown key renders the box with no logo.

The logo is fitted to whatever room the box has left once the label is placed, at
the mark's own aspect ratio — so give the node a box and let it size itself. `130x104`
suits one line of label; go to `150x120` for two lines or a wordmark you want larger.
Do not shrink `h` below ~80 with a label present, or the logo is squeezed into what
is left. Set `"text": ""` for a bare logo and the mark takes the whole box.

### `table` — an ER entity
```json
{ "id":"t1", "type":"table", "text":"CUSTOMER",
  "x":0, "y":0, "w":250,
  "accent":"#4b6cb7", "fill":"transparent",
  "stroke":"#4dabf7", "strokeWidth":1.6, "roughness":0,
  "fontFamily":"sans", "opacity":1, "seed":1003,
  "comment":"Customer master data",
  "cols":[
    { "name":"customer_id", "type":"int", "pk":true, "comment":"Unique customer id" },
    { "name":"name",        "type":"varchar(120)" },
    { "name":"country_code","type":"char(2)", "fk":true }
  ]}
```
- **Do not set `h`** — it is computed as `30 + rows × 23` and your value is discarded.
- `w` 230–260 fits most; widen for long column names.
- Column fields: `name`, `type`, `pk`, `fk`, `comment` (all optional except `name`).
- `comment` on the table or a column exports as SQL/Snowflake `COMMENT` and shows a dot indicator.
- Vary `accent` per table: `#4b6cb7` `#2f9e44` `#e8590c` `#6741d9` `#0c8599` `#c2255c`.
- If the schema already exists in Snowflake, the user does not need you to retype
  it: **☰ → Snowflake** signs in from the browser, lists a schema and imports the
  chosen tables with their real types, keys and comments. Point them at it rather
  than guessing column names.

> Tables render and load the same whether or not the app is in entity-diagram
> mode; that toggle only controls which editing tools the UI shows. A file full
> of `table` elements always opens correctly.

### `frame` — a labelled zone/boundary
```json
{ "id":"f1", "type":"frame", "text":"On-premise", 
  "x":-40, "y":-40, "w":420, "h":320,
  "stroke":"#8a90a2", "fill":"transparent", "roundness":1,
  "roughness":0, "fontFamily":"sans", "opacity":1, "seed":1004 }
```
Place frames **first** in `els` so they paint behind their contents.

### `note` · `text`
`note` = sticky note (default fill `#ffec99`, 170×150, `textAlign":"left"`).
`text` = bare label; set `w`/`h` roughly to the text extent.

### `draw` — freehand / painted stroke
A polyline through `points`, which are offsets **relative to the element's own
`x`/`y`** (so the first point is almost always `[0,0]`). Set `roughness: 0`.

```json
{ "id":"s1", "type":"draw", "x":100, "y":200,
  "points":[[0,0],[40,-18],[95,4],[150,-30]],
  "stroke":"#e03131", "strokeWidth":4, "roughness":0, "opacity":1 }
```

Optional painting fields — only add these when you actually want a painted
look, and never hand-write `widths` unless you mean it:

| Field | Meaning |
|---|---|
| `brush` | `"pen"` · `"pencil"` · `"crayon"` · `"charcoal"` · `"brush"` · `"marker"` · `"highlighter"` · `"airbrush"` · `"watercolor"`. `pencil`/`crayon`/`charcoal` render as scattered fibres (needs `widths`); `airbrush`/`watercolor` blur their own edge; `highlighter`/`watercolor` multiply over what is beneath them |
| `widths` | per-point stroke width, **must be exactly the same length as `points`**; omit for a constant-width stroke |
| `closed` | `true` joins the last point back to the first |
| `fill` | fill colour for a `closed` stroke (ignored otherwise) |
| `fillPattern` | pattern for that fill, same values as above |
| `blend` | `"normal"` · `"multiply"` · `"screen"` · `"overlay"` · `"darken"` · `"lighten"` — overrides the brush's own blending |

A mismatched `widths` length is ignored and the stroke falls back to constant
width, so when in doubt just omit it.

---

### Layers (optional)
A page may carry a `layers` array; every element then names one through
`layer`. Layers paint in array order — **first entry is the bottom** — and
each carries its own opacity, visibility and lock:

```json
{ "id":"p1", "name":"Page 1",
  "layers":[
    { "id":"l1", "name":"Background", "opacity":1, "visible":true, "locked":false },
    { "id":"l2", "name":"Ink",        "opacity":1, "visible":true, "locked":false }
  ],
  "els":[ { "id":"a", "type":"rect", "layer":"l1", "...":"..." } ] }
```

Omit `layers` entirely and FlowDiagram creates a single "Layer 1" and puts
everything on it, so this is safe to skip. If you do emit layers, every
element's `layer` must match one of the declared ids — an element naming a
layer that does not exist is silently moved to the bottom one.

Use layers when the diagram genuinely has separable passes: a faint reference
underlay, annotations over a finished diagram, alternative variants a reader
should be able to switch off. Do not split an ordinary diagram across layers
for no reason.

---

## 5. Arrows

```json
{ "id":"a1", "type":"arrow",
  "x":0, "y":0, "points":[[0,0],[100,0]],
  "stroke":"#4dabf7", "strokeWidth":2, "strokeStyle":"solid",
  "roughness":0, "routing":"elbow",
  "startArrow":"none", "endArrow":"arrow",
  "start":{ "id":"n1", "anchor":"auto" },
  "end":  { "id":"n2", "anchor":"auto" },
  "text":"", "fontSize":14, "fontFamily":"sans",
  "opacity":1, "seed":1005 }
```

- **`points` is required and must have ≥2 entries**, even when both ends are
  bound — a missing `points` array throws on load. When bound, the values are
  recomputed automatically; `[[0,0],[100,0]]` is a fine placeholder.
- `start`/`end` may be `null` for a free-floating arrow; then `points` are real
  coordinates relative to `x`,`y`.
- `routing`: `"elbow"` (right angles, and **the only value that routes
  around other shapes** — always write it explicitly, see §1) ·
  `"straight"` · `"curve"`
- `text` puts a label at the midpoint.

**Anchors:** `"auto"` (picks the side facing the other shape — **use this unless
you have a reason not to**) · `"n"` `"e"` `"s"` `"w"` · `"r:<rowIndex>:l"` /
`"r:<rowIndex>:r"` to bind to a specific **table column** (0-based, left or
right edge).

Prefer `"auto"` over a fixed side. A fixed side is a hint, not a guarantee: if
reaching it would mean crossing the shape's own body, the renderer slides the
attachment round to a side it can actually reach, so pinning a side that faces
away from the other shape simply gets overridden. Row anchors (`r:…`) are never
moved — they stay on their row.

**Arrowheads** (`startArrow` / `endArrow`):
`none` `arrow` `triangle` `tri_open` `diamond` `dia_open` `dot`
— ER crow's-foot: `one` `many` `onemany` `zeroone` `zeromany`

The crow's-foot markers draw as a three-prong fan that **opens toward the
entity it touches**, so put `many` on the end that binds to the child table,
never on the shaft side. `one` is a single crossbar, `zeroone` / `zeromany`
add the optionality circle behind it.

### Two connector conventions
**Structural FK** — solid, crow's foot, parent→child:
```json
"stroke":"#5c6b84","strokeWidth":1.6,"strokeStyle":"solid",
"startArrow":"one","endArrow":"many",
"start":{"id":"tCustomer","anchor":"r:0:r"},
"end":{"id":"tOrder","anchor":"r:1:l"}
```
Mark the child column `"fk": true` to match.

**Column lineage** ("this field feeds that field", no FK implied) — thin dashed teal:
```json
"stroke":"#0c8599","strokeWidth":1,"strokeStyle":"dashed",
"startArrow":"none","endArrow":"arrow"
```

---

## 6. Layout

Nothing auto-arranges on load, so lay elements out explicitly.

- Snap coordinates to multiples of **20**.
- **Flow diagrams:** left→right. Think in **gaps, not steps** — what matters is
  the clear space between one shape's right edge and the next shape's left
  edge, and a "200px step" between 200-wide shapes leaves a gap of zero.
  Leave **≥120px of gap** horizontally, and **≥60px of gap** vertically
  (y step 170 for 110-tall shapes).
- **ER diagrams:** grid, x step **330**, y step **260**.
- Frames: inset contents ~40px from the frame edge — but see the corridor rule
  below before putting a frame next to a connector.
- Start near `0,0`; negative coordinates are fine. The app zooms-to-fit on open.

If a layout gets tangled, the user can hit **☰ → Auto-arrange this page**.

### Leave the connectors somewhere to go

An elbow connector crossing between two columns makes its vertical jog at the
**midpoint of the gap between the two shapes**. That corridor has to be empty
or the route will not come out clean. Three consequences, each of which
produces the "arrows draped across the boxes" look when ignored:

- **Never let a frame edge land on the jog.** A frame inset 40px from its
  contents puts its border 40px into the gap. With an 80px gap the jog is at
  40px — exactly on the border — so every connector entering that group runs
  along the dashed line. When the target column sits inside a frame, make the
  gap **≥200px** so the jog clears the border with room to spare.
- **The router only tries one-bend routes.** It attempts a single vertical jog,
  at the midpoint or just outside the obstacles in the way; if neither is
  clear it gives up and draws the direct line straight *through* them. It will
  not find a clever multi-bend path around a crowded area, so keep the corridor
  empty rather than expecting it to be worked around.
- **Budget width for parallel connectors.** Connectors sharing a corridor sit
  side by side in it. Add roughly **40px of gap for each connector beyond the
  second**, or they bunch into one ambiguous line.

### Connector hygiene

- **Only join neighbours within a column.** A connector from row 1 to row 4 of
  the same stack has to get past rows 2 and 3 — it will either detour awkwardly
  or cut straight through them. If the meaning really is 1→4, reorder the stack
  so the two are adjacent.
- **Keep sources and targets in the same order.** If A sits above B on the left,
  A's target belongs above B's target on the right. Crossed connectors are
  nearly always a symptom of two columns ordered differently, not of the
  routing.
- **Anchor deliberately in a directional flow.** `"auto"` is right for a loose
  graph, but in a left→right pipeline write `"e"` on the source and `"w"` on
  the target (`"s"`/`"n"` for a top→bottom step). Every connector then leaves
  and arrives on the same face, so the result reads as parallel lines instead of
  a fan.
- **More than three connectors into one shape** means the corridor is doing too
  much work. Put a hub shape in the gap and route through that instead.

### Check the corridors before handing it over

Walk the arrow list once. For each arrow, ask whether the space between its two
shapes is clear of every *other* shape's bounding box, and whether any frame
border sits near the midpoint of that gap. Where it is not clear, move a shape
— do not hope the router copes, because in the one case it cannot solve it
draws through the obstruction rather than around it.

---

## 7. Icon keys for `node`

324 keys. Most are the vendor's own artwork, kept in the coordinate system the
vendor publishes it in — so a wordmark (`sap`, `oracle`, `aws`) is drawn wide and a
square mark is drawn square. You never size the logo yourself: give the node a
sensible box (130x104 fits one line of label) and the app fits the mark into it.

Use the exact key — an unknown `icon` renders the node with no mark at all.

**ERP / core business** (21)

`baan` `dynamics` `ecc` `epicor` `infor` `iscala` `jde` `netsuite` `odoo` `oracle`
`oracleebs` `peoplesoft` `qad` `s4hana` `sage` `sap` `sapbw` `sappo` `syteline`
`tally` `zoho`

**SaaS / CRM / collaboration** (20)

`asana` `atlassian` `hubspot` `intercom` `jira` `mailchimp` `marketo` `notion`
`salesforce` `sapariba` `servicenow` `sharepoint` `shopify` `slack` `stripe`
`sugarcrm` `teams` `twilio` `workday` `zendesk`

**Data platforms & databases** (45)

`aurora` `bigquery` `cassandra` `clickhouse` `cockroach` `couchbase` `database`
`databricks` `datacatalog` `datalake` `db2` `dremio` `druid` `duckdb` `dynamodb`
`elastic` `firebase` `flink` `hadoop` `hana` `hive` `iceberg` `influxdb` `mariadb`
`mongodb` `mssql` `mysql` `neo4j` `oracledb` `planetscale` `postgres` `presto`
`pulsar` `redis` `redshift` `singlestore` `snowflake` `snowpipe` `spark` `sqlite`
`supabase` `synapse` `teradata` `trino` `warehouse`

**BI / reporting** (12)

`dashboard` `datastudio` `excel` `grafana` `looker` `metabase` `microstrategy`
`powerbi` `qlik` `report` `superset` `tableau`

**Integration, pipelines & messaging** (73)

`adf` `airbyte` `airflow` `api` `apigee` `apigw` `as2` `athena` `boomi` `camel`
`camunda` `celigo` `confluent` `dagster` `datasphere` `dbt` `debezium` `edi` `esb`
`etl` `eventbridge` `events` `fabric` `fivetran` `ftp` `glue` `goldengate` `graphql`
`hevo` `ibmmq` `idoc` `informatica` `jitterbit` `kafka` `kinesis` `kong` `lambda`
`logicapps` `matillion` `meltano` `messaging` `mulesoft` `n8n` `nifi` `openflow`
`prefect` `pubsub` `qlikrep` `queue` `rabbitmq` `rest` `rivery` `sapslt` `segment`
`servicebus` `sftp` `snaplogic` `sns` `soap` `sqs` `ssis` `stepfn` `stitch` `stream`
`striim` `talend` `temporal` `tibco` `webhook` `webmethods` `websocket` `workato`
`zapier`

**Cloud** (53)

`alibaba` `amazonmq` `amplify` `appsync` `aws` `azure` `azuredevops` `azuresql`
`beanstalk` `blob` `cloudflare` `cloudformation` `cloudfront` `cloudgen` `cloudrun`
`cloudtrail` `cloudwatch` `codebuild` `codepipeline` `cognito` `digitalocean` `docker`
`documentdb` `ec2` `ecs` `eks` `elasticache` `elb` `fargate` `gcfunctions` `gcp` `gcs`
`glacier` `heroku` `iam` `ibmcloud` `k8s` `kms` `msk` `neptune` `netlify` `opensearch`
`quicksight` `rds` `route53` `s3` `secretsmgr` `spanner` `ssm` `vercel` `vpc` `waf`
`xray`

**Infrastructure, DevOps & observability** (33)

`activedirectory` `alert` `ansible` `auth0` `controlplane` `datadog` `dynatrace`
`firewall` `gateway` `github` `gitlab` `helm` `istio` `jenkins` `keycloak` `linux`
`loadbalancer` `monitor` `newrelic` `nginx` `okta` `onprem` `pagerduty` `prometheus`
`redhat` `scheduler` `security` `server` `splunk` `terraform` `vault` `vmware`
`windows`

**AI / LLM vendors and pipeline stages** (28)

`agent` `anthropic` `copilot` `embedding` `evaluator` `finetune` `gemini` `guardrail`
`huggingface` `humanloop` `knowledge` `knowledgegraph` `langchain` `llm` `mcpclient`
`mcpserver` `mistral` `nvidia` `ollama` `openai` `perplexity` `prompt` `qdrant`
`replicate` `retriever` `toolcall` `vectordb` `xai`

**Generic actors, files & languages** (39)

`ai` `apps` `barcode` `box` `clients` `computer` `csv` `developers` `devices` `dotnet`
`dropbox` `email` `factory` `file` `folder` `gear` `googledrive` `googlesheets` `java`
`json` `jupyter` `laptop` `mobile` `nodejs` `onedrive` `pdf` `person` `print`
`process` `python` `react` `springboot` `tablet` `truck` `user` `users` `webapp`
`word` `xml`

48 keys render a coloured wordmark chip rather than a logo — either the
vendor publishes no vector mark that may be redistributed, or the key names a
protocol or format that has no logo at all. They are still the right keys to use,
and the user can drop an official asset in themselves with the library's **+ Logo**
button:

`sapbw` `sappo` `ecc` `oracleebs` `baan` `iscala` `epicor` `dynamics` `netsuite` `jde`
`peoplesoft` `tally` `qad` `syteline` `sapariba` `synapse` `hana` `oracledb` `db2`
`rest` `soap` `ftp` `as2` `edi` `idoc` `boomi` `adf` `ssis` `hevo` `rivery` `meltano`
`debezium` `goldengate` `striim` `qlikrep` `sapslt` `datasphere` `dagster` `jitterbit`
`celigo` `webmethods` `logicapps` `servicebus` `confluent` `ibmmq` `fabric` `xml`
`json`

**Custom (imported logos)** — keys look like `my:acme-a1b2`. These are files the user
imported via **+ Logo**. You cannot invent them: only reference a `my:` key if you
can see it in a `.flow.json` the user gave you.

---
## 8. Starter — integration flow

```json
{"app":"FlowDiagram","version":1,"pages":[{"id":"p1","name":"Integration","els":[
{"id":"f1","type":"frame","text":"On-premise","x":-40,"y":-40,"w":320,"h":360,
 "stroke":"#8a90a2","fill":"transparent","roundness":1,"roughness":0,
 "fontFamily":"sans","opacity":1,"seed":1},
{"id":"n1","type":"node","icon":"sap","text":"SAP S/4HANA","x":0,"y":0,"w":130,"h":104,
 "stroke":"#4dabf7","fill":"transparent","roundness":1,"roughness":0,
 "fontSize":13,"fontFamily":"sans","textAlign":"center","opacity":1,"seed":2},
{"id":"n2","type":"node","icon":"sftp","text":"SFTP landing","x":0,"y":160,"w":130,"h":104,
 "stroke":"#4dabf7","fill":"transparent","roundness":1,"roughness":0,
 "fontSize":13,"fontFamily":"sans","textAlign":"center","opacity":1,"seed":3},
{"id":"n3","type":"node","icon":"boomi","text":"Boomi iPaaS","x":320,"y":80,"w":130,"h":104,
 "stroke":"#4dabf7","fill":"transparent","roundness":1,"roughness":0,
 "fontSize":13,"fontFamily":"sans","textAlign":"center","opacity":1,"seed":4},
{"id":"n4","type":"node","icon":"snowflake","text":"Snowflake","x":580,"y":80,"w":130,"h":104,
 "stroke":"#4dabf7","fill":"transparent","roundness":1,"roughness":0,
 "fontSize":13,"fontFamily":"sans","textAlign":"center","opacity":1,"seed":5},
{"id":"a1","type":"arrow","x":0,"y":0,"points":[[0,0],[100,0]],
 "stroke":"#4dabf7","strokeWidth":2,"strokeStyle":"solid","roughness":0,"routing":"elbow",
 "startArrow":"none","endArrow":"arrow","start":{"id":"n1","anchor":"auto"},
 "end":{"id":"n3","anchor":"auto"},"text":"IDoc","fontSize":14,"fontFamily":"sans",
 "opacity":1,"seed":6},
{"id":"a2","type":"arrow","x":0,"y":0,"points":[[0,0],[100,0]],
 "stroke":"#4dabf7","strokeWidth":2,"strokeStyle":"solid","roughness":0,"routing":"elbow",
 "startArrow":"none","endArrow":"arrow","start":{"id":"n2","anchor":"auto"},
 "end":{"id":"n3","anchor":"auto"},"text":"CSV","fontSize":14,"fontFamily":"sans",
 "opacity":1,"seed":7},
{"id":"a3","type":"arrow","x":0,"y":0,"points":[[0,0],[100,0]],
 "stroke":"#4dabf7","strokeWidth":2,"strokeStyle":"solid","roughness":0,"routing":"elbow",
 "startArrow":"none","endArrow":"arrow","start":{"id":"n3","anchor":"auto"},
 "end":{"id":"n4","anchor":"auto"},"text":"Load","fontSize":14,"fontFamily":"sans",
 "opacity":1,"seed":8}
]}]}
```

## 9. Starter — ER model

```json
{"app":"FlowDiagram","version":1,"pages":[{"id":"p1","name":"Schema","els":[
{"id":"t1","type":"table","text":"CUSTOMER","x":0,"y":0,"w":250,
 "accent":"#4b6cb7","fill":"transparent","stroke":"#4dabf7","strokeWidth":1.6,
 "roughness":0,"fontFamily":"sans","opacity":1,"seed":1,
 "comment":"Customer master",
 "cols":[{"name":"customer_id","type":"int","pk":true,"comment":"Unique id"},
         {"name":"name","type":"varchar(120)"}]},
{"id":"t2","type":"table","text":"SALES_ORDER","x":330,"y":0,"w":250,
 "accent":"#2f9e44","fill":"transparent","stroke":"#4dabf7","strokeWidth":1.6,
 "roughness":0,"fontFamily":"sans","opacity":1,"seed":2,
 "cols":[{"name":"order_id","type":"int","pk":true},
         {"name":"customer_id","type":"int","fk":true},
         {"name":"order_date","type":"date"}]},
{"id":"r1","type":"arrow","x":0,"y":0,"points":[[0,0],[100,0]],
 "stroke":"#5c6b84","strokeWidth":1.6,"strokeStyle":"solid","roughness":0,
 "routing":"elbow","startArrow":"one","endArrow":"many",
 "start":{"id":"t1","anchor":"r:0:r"},"end":{"id":"t2","anchor":"r:1:l"},
 "text":"","fontSize":14,"fontFamily":"sans","opacity":1,"seed":3}
]}]}
```

---

## 10. Check before delivering

- [ ] Valid JSON; top-level `pages` array present
- [ ] Every element has a unique `id`
- [ ] **Every element with text has `"fontFamily": "sans"`**
- [ ] Every `arrow`/`line` has a `points` array with ≥2 entries
- [ ] Every `start.id` / `end.id` matches a real element id
- [ ] Table `h` is **not** set; table `w` **is** set
- [ ] `r:N:l|r` row indexes are within that table's `cols` range
- [ ] `icon` values are from §7
- [ ] Frames appear before their contents in `els`
- [ ] Coordinates are spaced out, not all at `0,0`
- [ ] 3-D solids (`cube` `tube` `sphere` `cone` `pyramid`) have a real `fill`,
      not `"transparent"`
- [ ] If `layers` is present, every element's `layer` matches a declared
      layer `id`
- [ ] Any `widths` array is exactly as long as its `points` array
- [ ] `many` / `onemany` / `zeromany` sit on the child end of a relationship
- [ ] A share link was built (§11), is under ~8,000 characters, and was opened
      once to confirm it loads

---

## 11. Give the user a clickable link

FlowDiagram has no server and no accounts — a shared diagram travels **inside the
URL fragment**, so the link *is* the document. That means you can hand the user a
link they click once to see the diagram you just generated, instead of asking them
to find the file and use **☰ → Open .flow.json**.

This is half the deliverable, not a bonus: every diagram request ends with the
file *and* the link. The link is the fast path; the file is what survives.

### The format

```
https://flowdiagram.net/#d=z<base64url>
```

`<base64url>` is the JSON document text, **raw-deflated** (`deflate-raw`, i.e. a
bare DEFLATE stream with no zlib or gzip header), then base64 with `+`→`-`,
`/`→`_`, and trailing `=` padding stripped.

Encode **the same JSON you wrote to the file** — no rewrapping. The reader only
looks at `pages` (plus optional `name` and `customIcons`), so a `.flow.json`
file's exact text works verbatim.

### Build it with the built-in browser

Both Claude Code and Codex ship a browser tool that can evaluate JavaScript in a
page, and every modern browser has `CompressionStream('deflate-raw')` built in.
That is the recipe: no libraries, no install, and it is the same code path the
app's own **Share** button uses, so the link is byte-identical to one a human
would produce.

Open any page in the browser tool (`https://flowdiagram.net/` is the obvious one
— you can then verify the link in the same tab) and evaluate:

```js
// flowJson: the exact text you wrote to the .flow.json file
const cs = new CompressionStream('deflate-raw');
const w = cs.writable.getWriter();
w.write(new TextEncoder().encode(flowJson));
w.close();
const bytes = new Uint8Array(await new Response(cs.readable).arrayBuffer());
let s = '';
for (let i = 0; i < bytes.length; i += 0x8000)          // chunked: apply() blows
  s += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));  // the stack
const code = btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
'https://flowdiagram.net/#d=z' + code;
```

If your browser tool does not allow top-level `await`, wrap the body in
`(async () => { … })()` and await the returned promise.

**Verify before you hand it over.** Navigate the same browser tab to the link you
built. It worked if the fragment has been cleared from the address bar (the app
strips it once consumed) and the new page is on screen. A link that cannot be
decoded toasts *"That link could not be opened"* and leaves the hash in place.

### Fallbacks, in order

**Node** — same format, no browser needed. `zlib.deflateRawSync` is exactly the
same stream:

```js
import { deflateRawSync } from 'node:zlib';
import { readFileSync } from 'node:fs';
const code = deflateRawSync(readFileSync('diagram.flow.json'))
  .toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
console.log('https://flowdiagram.net/#d=z' + code);
```

Python: `base64.urlsafe_b64encode(zlib.compress(data)[2:-4]).rstrip(b'=')`, or
cleanly with `zlib.compressobj(wbits=-15)`.

**No compression at all** — the `j` tag takes plain base64url of the JSON, so you
can build it with nothing but a base64 encoder:

```
https://flowdiagram.net/#d=j<base64url of the raw JSON>
```

It is roughly 4–6× longer than the `z` form, so only reach for it when you have
neither a browser nor a runtime.

**Mermaid, by hand** — deliberately uncompressed so a link can be written with
nothing but `encodeURIComponent`:

```
https://flowdiagram.net/#mermaid=<URL-encoded Mermaid source>
```

Also `#dbml=…` and `#sql=…` for schemas. These import through the corresponding
converter, so you lose logos, zones and comments — use `#d=` when the diagram has
any of those.

### What the finished reply looks like

Two lines. The path, then the link — nothing else about the mechanics:

> Wrote the integration landscape to `docs/diagrams/sap-to-snowflake.flow.json`
> (9 systems, 3 zones).
>
> [Open it in FlowDiagram](https://flowdiagram.net/#d=z1ZLBauMwEIZfZfn3Kljb…)

Do not explain deflate, base64, or the browser step — that is your plumbing, not
theirs. Do not offer the link as an alternative to the file or the file as an
alternative to the link; give both and let them pick.

### Rules

- **Keep the whole URL under ~8,000 characters.** Past that some chat and mail
  clients silently truncate it and the link dies. If you go over: share one page
  instead of all of them, or just deliver the file. Check `url.length` — you
  already have it in hand.
- **Round the numbers before you encode**, if you laid the diagram out with a
  positioning algorithm rather than typing round coordinates by hand. `x`, `y`,
  `w`, `h` to the nearest pixel; everything else (`angle`, arrow `points`) to one
  decimal place. The app's own Share button does exactly this — it is the
  single biggest lever on link length, since a computed layout's long float
  coordinates rarely repeat and so barely compress, where whole numbers do. No
  visible difference in the result.
- **Encode only the pages you mean to share**, and only the custom icons those
  pages actually use. An unreferenced imported logo is hundreds of KB of base64
  on its own.
- **Present it as a markdown link with a short label** — `[Open the diagram in
  FlowDiagram](https://flowdiagram.net/#d=z…)`. Never paste the bare base64 into
  chat as text; it is noise, and long lines get wrapped and broken.
- **Opening a link is additive.** The pages are appended to whatever document the
  user already has open, with fresh ids, and their work is never replaced. Say so
  if they seem worried about clicking it.
- **Still write the file.** A URL is not a document: it cannot be diffed,
  committed, or reopened after the chat scrolls away.

---

## 12. Installing this file

It's plain Markdown — copy it wherever your tool reads project instructions:

| Tool | Location |
|---|---|
| Claude Code | `.claude/skills/flowdiagram/SKILL.md` (per project) or `~/.claude/skills/flowdiagram/SKILL.md` (all projects) — keep the frontmatter |
| Codex | `AGENTS.md` at the repo root (per project) or `~/.codex/AGENTS.md` (all projects) — delete the frontmatter |
| OpenCode / Amp | `AGENTS.md` (or append to an existing one) |
| Cursor | `.cursor/rules/flowdiagram.mdc` or `.cursorrules` |
| GitHub Copilot | `.github/copilot-instructions.md` |
| Zed | `.rules` |
| Windsurf | `.windsurfrules` |
| Anything else | paste into the system prompt / custom-instructions box |

The `---` frontmatter block at the top is only used by Claude Code. Other tools
ignore it; delete it if it renders as visible text.
