Network diagrams as code: Mermaid for network engineers
A working reference for documenting a network in text instead of a drawing tool. The flowchart grammar, the patterns for campus topology, the sequence and state diagrams worth knowing, and the Python that generates the whole thing from LLDP neighbor data.
- Write a network topology in Mermaid from scratch, with real interface names on the links.
- Pick between a flowchart, a sequence diagram, and a state diagram for a given job.
- Generate a topology diagram from neighbor data with plain Python.
What is a network diagram as code?
A network diagram as code is a topology written in plain text and stored in git next to the configs it describes. You declare the devices and the links, and a layout engine draws the picture. Mermaid is the version of this that already renders in GitHub, GitLab, Notion, Obsidian, and VS Code, with no plugin and no export step.
The problem it solves is drift. Network diagrams go stale the moment someone saves them, because the network keeps changing and the drawing does not. Nothing connects “I added a switch” to “I opened the drawing file and moved a box.” So the diagram on the wiki describes a network that stopped existing sometime last spring, and the network team stops trusting it.
Text fixes the root cause in two steps. First, the diagram lives in the same repository as the change, so it diffs in the pull request and a removed link is a removed line. Second, a diagram made of text is a string, which means the Python you already know can write it for you from live device data.
Everything below is the working reference: the grammar, the network patterns, and the generator.
The grammar, in about five minutes
A Mermaid flowchart is a direction, then one statement per line.
flowchart LR
core["core-sw01"]
acc["acc-sw01"]
core --- acc
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart LR
core["core-sw01"]
acc["acc-sw01"]
core --- accFour things are going on there.
flowchart LRsets the direction.LRis left to right,TDis top down. Campus topology usually reads best asTD, and a path or a chain usually reads best asLR.coreis the node id. It has to be machine safe, so no dots, no slashes, no spaces. This is what you refer to in link lines.["core-sw01"]is the human label, in quotes. Quoting it means you can put a hostname with dots or a slash in it and Mermaid will not try to parse the punctuation.---is a link. Solid and undirected, which is what you want for a physical link between two devices. Use-->when the arrow means something, like traffic flow or a decision path.
The id and the label being separate is the detail people trip on first. core-sw01 is not a legal id, because the hyphen means something to the parser. So the id gets sanitized and the label keeps the real name.
edge-rtr01.corp.example.com cannot be used as an id as written. The dots and the hyphen come out of the id, leaving something like edge_rtr01, and the full name goes in the quoted label where the punctuation does no harm.
Put the interfaces on the wire
Text between pipes becomes the edge label, and that is where the diagram earns its keep over a drawing:
core ---|"Gi1/0/1 to Gi1/0/48"| acc
Link styles carry meaning too. A thick link marks an uplink, a dotted link marks a backup path:
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
edge["edge-rtr01"]
core1["core-sw01"]
core2["core-sw02"]
acc1["acc-sw01"]
edge ===|"Te1/1/1"| core1
core1 ---|"Po1"| core2
core1 ---|"Gi1/0/1 to Gi1/0/48"| acc1
core2 -.-|"standby"| acc1How do I draw a campus topology?
Group devices with subgraph. One subgraph per site, per closet, or per tier, whichever way your team already talks about the network.
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
subgraph wan["WAN edge"]
edge["edge-rtr01"]
end
subgraph dc["Core"]
core1["core-sw01"]
core2["core-sw02"]
end
subgraph idf2["Floor 2 IDF"]
acc1["acc-sw01"]
acc2["acc-sw02"]
end
edge ===|"Te1/1/1"| core1
edge ===|"Te1/1/2"| core2
core1 ---|"Po1"| core2
core1 ---|"Gi1/0/1"| acc1
core1 ---|"Gi1/0/2"| acc2
core2 ---|"Gi1/0/1"| acc1
core2 ---|"Gi1/0/2"| acc2Subgraphs need an id and a quoted label, same as nodes, and they close with end.
Before you use these on your own topology, look at the diagram above: every link in it crosses a subgraph boundary, and none of them needed extra syntax to do it. A subgraph groups nodes for the reader and does nothing else. It does not constrain what can link to what. If you expected the boundary to mean something, drop that assumption now rather than after you have drawn forty of these.
One piece of practical advice on scope. A campus with 200 access switches produces a diagram nobody can read, and no amount of layout tuning fixes it. Generate one diagram per site or per closet, and keep a separate small diagram for the core. Splitting by site is free when the diagram is generated, because it is a filter on the data rather than an afternoon of rearranging boxes.
When should I use a sequence or state diagram?
Flowcharts answer “what connects to what.” Two other diagram types answer questions a flowchart handles badly, and both are worth the ten minutes it takes to learn them.
A sequence diagram shows the order of messages between participants. It is the right shape for a protocol exchange, a failover test, or an onboarding flow:
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
sequenceDiagram
participant C as Client
participant R as Relay agent
participant S as DHCP server
C->>R: DISCOVER, broadcast
R->>S: DISCOVER, unicast with giaddr set
S->>R: OFFER
R->>C: OFFER
C->>R: REQUEST
R->>S: REQUEST
S->>R: ACK
R->>C: ACKA state diagram shows the states one thing moves through and what triggers each move. It is the right shape for a lifecycle:
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
stateDiagram-v2
[*] --> Blocking
Blocking --> Listening: selected as designated or root port
Listening --> Learning: forward delay expires
Learning --> Forwarding: forward delay expires
Forwarding --> Blocking: better BPDU received
Blocking --> [*]: port goes downHow do I generate a diagram from LLDP output?
Start with neighbor data normalized into a list of links. However you collect it, with Netmiko, RESTCONF, or a parser over show lldp neighbors detail, the shape you want is this:
neighbors = [
{"local": "acc-sw01", "local_intf": "Gi1/0/48",
"remote": "core-sw01", "remote_intf": "Gi1/0/1"},
{"local": "acc-sw02", "local_intf": "Gi1/0/48",
"remote": "core-sw01", "remote_intf": "Gi1/0/2"},
{"local": "core-sw01", "local_intf": "Te1/1/1",
"remote": "edge-rtr01", "remote_intf": "Gi0/0/0"},
]
The generator is three steps: sanitize hostnames into legal ids, declare each device once, then write one edge per link.
def node_id(hostname):
"""Turn a hostname into a Mermaid-safe id."""
return hostname.replace(".", "_").replace("-", "_")
def to_mermaid(neighbors):
lines = ["flowchart TD"]
# Declare each device once, in first-seen order.
seen = []
for link in neighbors:
for host in (link["local"], link["remote"]):
if host not in seen:
seen.append(host)
lines.append(f' {node_id(host)}["{host}"]')
# One edge per link, both interface names on the label.
for link in neighbors:
label = f'{link["local_intf"]} to {link["remote_intf"]}'
lines.append(
f' {node_id(link["local"])} ---|"{label}"| '
f'{node_id(link["remote"])}'
)
return "\n".join(lines)
print(to_mermaid(neighbors))
That prints:
flowchart TD
acc_sw01["acc-sw01"]
core_sw01["core-sw01"]
acc_sw02["acc-sw02"]
edge_rtr01["edge-rtr01"]
acc_sw01 ---|"Gi1/0/48 to Gi1/0/1"| core_sw01
acc_sw02 ---|"Gi1/0/48 to Gi1/0/2"| core_sw01
core_sw01 ---|"Te1/1/1 to Gi0/0/0"| edge_rtr01
Which renders as:
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
acc_sw01["acc-sw01"]
core_sw01["core-sw01"]
acc_sw02["acc-sw02"]
edge_rtr01["edge-rtr01"]
acc_sw01 ---|"Gi1/0/48 to Gi1/0/1"| core_sw01
acc_sw02 ---|"Gi1/0/48 to Gi1/0/2"| core_sw01
core_sw01 ---|"Te1/1/1 to Gi0/0/0"| edge_rtr01If you want to see this run against your own network before you write any code, the network diagram generator does exactly the above in your browser: paste the output, read the link table it extracted, copy the Mermaid.
Write the output to topology.mmd, commit it, and the diagram is as fresh as the last collection run. Put the collection in cron or in CI and the diagram updates itself. The engineer who owns it does not have to remember, because updating it is no longer a manual step.
When is a drawing tool still the right answer?
Diagrams as code is not a rule that every picture has to become text. Two categories are still better served by a drawing tool, and pretending otherwise wastes your time.
Physical space. Rack elevations, cable runs, floor plans, and anything where the position of a thing on the page corresponds to its position in the building. Mermaid decides placement for you, which is the whole point everywhere else and exactly wrong here.
Polished one-time deliverables. The architecture diagram in a proposal, the executive one-pager, the slide the CIO sees. These need visual control that a layout engine will not give you, and they are drawn once and thrown away, so drift is not a problem.
Those are the diagrams you maintain by hand, forever, with your own discipline as the only thing keeping them current. That is fine for two diagrams. Your whole topology is a different scale of problem.
Can AI draw my network diagram?
A model drafts Mermaid faster than you can type it, and three uses pay off right away.
- Data to draft. Paste a neighbor table or an inventory export and ask for the flowchart. You get a renderable draft in seconds instead of ten minutes of careful typing.
- One diagram type to another. You wrote a failover procedure as a flowchart and realize halfway through that it is really a message exchange, so it should be a sequence diagram. Converting between them is mechanical restructuring, which models do well.
- Syntax to English. You inherit a diagram from a teammate and hit syntax you have not seen. Paste it and ask what each line does, then verify by changing a line and rerendering.
There are two failure modes, and they are not equally dangerous.
Invented syntax is the first. The model writes an arrow token that does not exist, the renderer refuses, and you find out in one second. This one is harmless.
Rendering cleanly while contradicting your data is the second. A link that is not in the neighbor table, a device on the wrong tier, an interface name transposed between two ports. The picture looks professional, so it gets committed, and now it is wrong in a way that is harder to catch than a stale diagram, because it looks fresh.
What does that failure look like?
Here is one, small enough to check by eye. The neighbor data has two links:
core-sw01 Gi1/0/1 <-> acc-sw01 Gi1/0/48
core-sw01 Gi1/0/2 <-> acc-sw02 Gi1/0/48
And here is the diagram a model produced from it.
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
core["core-sw01"]
acc1["acc-sw01"]
acc2["acc-sw02"]
core ---|"Gi1/0/1 to Gi1/0/48"| acc1
core ---|"Gi1/0/2 to Gi1/0/48"| acc2
acc1 ---|"Gi1/0/47"| acc2The neighbor data reported two links. The diagram has three.
The extra link, between the two access switches, is invented. Nothing in the neighbor tables reported it. The model drew the shape an access layer usually has instead of the shape this data describes, and because that shape is normal, the picture looks right.
The cost is operational, not cosmetic. That diagram says acc-sw01 has a path that survives losing the core. It does not. Plan a maintenance window off this picture and you isolate a floor.
Corrected, with only the links the data reported:
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
core["core-sw01"]
acc1["acc-sw01"]
acc2["acc-sw02"]
core ---|"Gi1/0/1 to Gi1/0/48"| acc1
core ---|"Gi1/0/2 to Gi1/0/48"| acc2Two checks catch both failure modes. Render it, then compare it line by line against the data it came from. If you generated the diagram from data with code, the second check is the code review, which is a better place for it.
The cheat sheet
| You want | Write |
|---|---|
| Top-down flowchart | flowchart TD |
| Left-to-right flowchart | flowchart LR |
| A device | core1["core-sw01"] |
| A physical link | a --- b |
| A link with interfaces | a ---|"Gi1/0/1 to Gi1/0/48"| b |
| An uplink, drawn thick | a === b |
| A backup path, drawn dotted | a -.- b |
| A directional flow, where the arrow means something | a --> b |
| Group into a site or tier | subgraph idf2["Floor 2 IDF"] … end |
| Protocol message order | sequenceDiagram |
| A message | C->>S: DISCOVER |
| A lifecycle | stateDiagram-v2 |
| A transition | Listening --> Learning: forward delay expires |
| Start or end state | [*] |
Where to go from here
The lesson version of this is free on RouteSwitchU, and it includes the parts a reference page cannot give you: a terminal recording of a topology drawing itself from neighbor data, a Python box you can run in the browser, and a graded lab that checks the four generator functions you would need to build this for real. It asks for a free account and nothing else.
The lesson sits inside the AI-Assisted Network Automation course, where the same three moves (collect the data, put it in git, generate the artifact) get pointed at configuration, validation, and change records instead of pictures. Diagrams are the low-risk version. Nothing breaks if the diagram is wrong on the first try, which is why they are a good place to start.
If you want the collection side first, Discover and document the network is where the neighbor data in this article comes from, and Version the source of truth in git is what makes the diff-in-a-pull-request part work.