Files and templates

New, open, save

The File group in the toolbar holds New, Open, Save and Revisions. Templates lives in the app menu, and the gallery appears by itself on an empty canvas — in Classic; Studio's empty page has three doors instead (below).

Action How Notes
New New button Replaces the document with a single empty page. It is undoable — ⌘/Ctrl Z brings the previous document back.
Open Open button, ⌘/Ctrl O Opens .drawio (also .xml) and Visio .vsdx — from this computer, or from the cloud you connected (see below).
Save Save button, ⌘/Ctrl S Writes back where the document came from, in the format it is already in — the button's tooltip names that format.
Save As ⇧⌘/Ctrl S The extension you pick in the dialog decides the format: choose .vsdx and the document saves as Visio from then on.

Open and Save As start in the folder you used last. In the desktop app that is the folder of the last drawing you opened or saved, and it is remembered across restarts; exports keep a folder of their own, which starts where your drawings are until you export once. Chrome and Edge remember the folder the same way for Open and Save As. A folder that has since been deleted, or is on a drive that is not connected, is skipped and the dialog starts where your system would put it.

A page is named from its tab in the pages bar along the bottom — double-click the tab, type, press Enter. That name is also the suggested filename when you export the page (path-hostile characters are folded to -, an empty name becomes diagram). The window title shows the open file's name, with a dot while there are unsaved changes; until the document has been saved to a file it shows the active page's name instead.

Recent files

The files you open or save on this device are listed newest first, up to eight: in Classic, under the small arrow beside Open; in Studio, under Open in the document name's menu and on the empty page. Press one to open it again. A file you open again moves back to the top rather than appearing twice. If a file has been moved, renamed or deleted since, it is taken off the list and a message says so.

In the desktop app the list keeps each file's place on disk. In a browser it keeps what the browser allows: Chrome and Edge remember the file itself, and may ask once whether Drawbridge can open it again; a browser without that ability (Firefox, Safari) lists nothing, and Open still works as always. Files from a connected cloud are opened from the cloud's own list, and a file you dropped onto the window cannot be opened again from here, so neither is listed.

Studio's empty page

In Studio an empty page offers three ways in, side by side:

Or press Start with a blank canvas under them for an empty page with nothing placed, or press D and start drawing. Classic shows the template gallery on an empty page, as it always has.

A page you add with New page opens blank instead — the page you asked for is a page to draw on. One quiet line at the top, Start from a paste, a file or a template…, puts the three ways in back if you want them, and it goes as soon as anything is on the page. Come back to that page while it is still empty and it is still blank; the first page of a new document always greets you with the three ways in.

What you can open

Files are recognised by extension, and a file that is really a zip (a renamed .vsdx) is still read as Visio.

You can also drag a .drawio or .vsdx straight onto the canvas to open it, instead of going through Open. It asks the same question first if the current drawing has unsaved changes, and it opens the file as a document — it does not paste it into what you already have. One difference from Open: a dropped file gives the app its contents but not permission to write back to it, so Save will ask you where to put it rather than saving over the file you dropped. Dropping an image still adds the image to the drawing, as it always has.

Copy and paste between Drawbridge and draw.io

Copy here, paste there — and the other way round. ⌘/Ctrl C puts the selection on the clipboard in draw.io's own format, so a paste into draw.io arrives as the shapes you copied: the tiles in their colours, the icons, the names and the cables between them, connected. It used to arrive as one text box holding the app's internal notation, which is what somebody who copies out of Drawbridge every day would see first. Pasting the other way reads the same language: copy in draw.io — their shapes or a file of yours opened over there — press ⌘/Ctrl V here, and the cells become devices and cables in one undo step. Between two Drawbridge windows nothing is lost at all, down to a cable whose far device you did not copy, which stays attached to the device it already names. And a clipboard holding something that is not a drawing — a line of terminal output, a column from a spreadsheet — pastes nothing and says so, rather than quietly repeating the last thing you copied.

Compressed legacy draw.io pages

Older draw.io versions stored each page's payload deflate-compressed and base64-encoded. Drawbridge decompresses those on open, so a legacy file's pages arrive as ordinary editable pages — nothing marks them as having been compressed. Saving writes them back as plain XML, which is also what a current draw.io writes by default, so the file only gets easier to read.

A payload that cannot be decoded — a damaged file, or compression draw.io never wrote — opens as a read-only page instead: its tab is marked "Unreadable page — read-only", it shows empty, and its original content is preserved verbatim so saving the file does not destroy it. Other pages in the same file open normally. If no page at all can be read, the open fails with a message telling you to re-save the file with a current draw.io and open it again.

Auto-save

Two things happen automatically, both debounced about a second after you stop editing:

Auto-save to the file can be turned off under the app menu (Auto-save). An untitled document is never written to disk.

A drawing that comes back from the snapshot and is in no file — an untitled one you never saved, or edits that never reached their file — still counts as work you could lose. Open, New, a file launched from your computer and a file dropped onto the canvas all ask before replacing it, and the question says the drawing came back from autosave. It shows no unsaved dot and does not stop the window closing, because the snapshot keeps it either way. Save it once and the question stops.


A connected cloud

Once you have connected Google Drive, Microsoft OneDrive or Dropbox under ☰ → Storage… and picked it there, the same buttons work against that cloud: If your workplace has to approve it first. Signing in with a work or school account may end at a Microsoft screen headed Approval required rather than at your files. That is your organisation's policy, not a fault in Drawbridge: many companies only let staff connect apps an administrator has approved. You can send the request from that screen, and your IT administrator approves Drawbridge once for everyone. A personal Microsoft account is not affected, and Google Drive and Dropbox have their own equivalents of this policy. Drawbridge asks for the narrowest access each provider offers — its own folder, and nothing else in your storage — which is the thing worth telling an administrator who asks.

Every write is conditional: it goes through only if the file in the cloud is still the version this document was read from. If something else saved a newer version in between — you on another computer, a colleague with the folder shared — nothing is overwritten. A Save asks straight away; auto-save pauses and a message that does not fade offers Resolve…. Either way you choose:

Offline, auto-save pauses with a message and resumes by itself when the connection is back; nothing is lost in between, because the local snapshot is still written. In this version the cloud rows are for the web app; the desktop app lists them as coming later.

In the browser, write permission for a file handle can lapse. Auto-save then pauses rather than prompting from a background timer, and a sticky toast offers Re-enable — clicking it re-requests permission and resumes. If a write genuinely fails you get an "Auto-save failed" message once, and auto-save pauses until you retry.

If you close a browser tab with unsaved changes, the browser's own "leave site?" prompt appears. The desktop app never blocks the close — the snapshot has already been written and is restored on next launch.

Desktop vs browser

Desktop app Browser
Dialogs Native OS open/save dialogs File System Access API where available (Chromium)
Saving Writes straight to the path Writes through the file handle
No handle available — Falls back to a plain download; the document stays marked unsaved because the browser reports no completion, and nothing auto-saves
After relaunch The document reattaches to its file automatically The binding is lost; the local snapshot still restores the content
Opening from the OS Double-click a .drawio, "Open with", or a path on the command line opens it in the app —

Firefox and Safari have no File System Access API and have said they will not add one; Brave ships with it switched off (brave://flags/#file-system-access-api turns it on); and Chrome and Edge offer it only on https:// pages and localhost. In all of those cases the app can still open files through a file picker and save — as a download, a new file each time — but there is no file binding, so nothing auto-saves to disk. Save there asks the file's name — the first time you save a drawing, and on every Save as… — and a name ending in .vsdx saves Visio; the folder is the browser's to choose (how to choose it). Save in place needs Chrome or Edge on the https:// site, the desktop app, or a connected cloud storage, which saves in place in every browser.

On relaunch the desktop app reattaches to the last file without reading it — the local snapshot is at least as new — so nothing on disk is overwritten until your next edit. If the file has been moved or deleted, the binding is dropped silently and the document becomes untitled.

Templates

Press Templates… in the app menu, or pick one from the gallery shown on an empty canvas. The gallery steps aside the moment you start drawing — arming a tool, placing a shape, or typing the first text block or callout — and comes back if the page ends up empty again.

Built-in templates are grouped by category: Datacenter, Campus, Branch, SD-WAN, Global and OT — for example "DC — Spine/Leaf", "Campus — Collapsed Core", "Branch — SD-WAN", "SD-WAN — Hub & Spoke", "Global — AMER / EMEA / APAC" and "OT — Purdue Plant". Each card shows a generated thumbnail.

The three regional templates — AMER, EMEA and APAC — open on a map cropped to their own region, and the map is sized so the whole page fits a window: the region still fills its box, and the device names are readable the moment the template opens rather than after you zoom in. The map is a backdrop on its own layer, so you can switch it off or drag the sites onto their real cities.

Applying a template replaces the current diagram. If the canvas is not empty you are asked to confirm; the change is undoable either way. Built-in templates are drawn with your current default connector type, exactly as if you had drawn the cables yourself.

Generate an SD-WAN topology

The SD-WAN category has a Generate… card. It opens a short wizard — how much detail, regions, whether hubs mesh across regions, hubs per region, branches per region, WAN links per branch and their types (Internet / MPLS / LTE), single or HA branch firewalls, and the branch LAN (none, switch, switch + AP) — with a live preview and a running count of boxes, tunnels and links, and drops the generated topology in as a diagram.

The first question decides how much of the network is drawn:

Saving and sharing your own

Custom templates live in local storage on this machine, not in the diagram file.

Importing devices from a list

Import opens a paste box. Paste terminal output or a CSV and press Import — the devices and links land on the current page, laid out automatically, next to whatever is already there, and the view zooms to fit.

There is no format picker: the parser works out what you pasted and tells you what it decided. Output from a vendor it does not read is reported as not recognised, with the commands it does read listed, rather than being guessed at — a neighbour table read as the wrong vendor's is a drawing that looks right and is not. Recognised formats:

Where the output says nothing about a device — no show command prints what the box you ran it on is, and a summary table has no capability column — the icon is read from the name, last of all: CORE-SW-01 is a switch, 8325-spine an L3 switch, AP-LOBBY-01 an access point, and a name that says nothing keeps the neutral tile. Anything the output does state wins over it.

Neighbour output is drawn in the three-layer model. CDP and LLDP say who is cabled to whom and nothing about where anything belongs, so Drawbridge draws it the way the network is built: core switches on the top row, distribution below them, access below that, read from the names — core (or spine), dist or agg (aggregation is the distribution layer by another name), access — and a switch whose name says none of these is an access switch, which almost every switch in a campus is. Routers and firewalls sit above the core. Inside a layer the rows follow the cabling: an aggregation switch hangs a row under the distribution switch it uplinks to, a daisy-chained access switch a row under the one it hangs off, and two core switches cabled to each other sit side by side. The endpoints — access points, phones, PCs, anything with a single cable — are drawn in a block directly under the switch they plug into, in that switch's port order, instead of in one endless row. It does not matter which switch's capture you pasted first: one access switch's output draws its dist-… uplink above it and its endpoints below, and a whole site draws the same picture whatever order the captures come in. Part of a paste with no layer in any name is drawn from its best-connected switch down. DOT, Mermaid, Containerlab and the other formats that state a shape of their own keep it — with one exception, the endless row: a device with six or more endpoints hanging off it by a single cable each (a hypervisor's VMs, a switch's access points) draws them in a block under it there too, in port order, and when they stand in different zones, one block per zone, so each zone's box wraps only its own. Zones those formats declare — DOT clusters with a kind=, Mermaid subgraphs — are laid out as blocks of their own, so a zone holds its members and nothing else and its cables come in from outside it, except in a source that also declares a same-row pair (an MC-LAG's peer link), which keeps the layout it had.

CSVs are matched by header name, not column order, so an export from most inventory tools lines up. Recognised device columns include name/hostname/label, kind/type, role, management IP, serial, model/platform, location/site/rack and notes. Link columns include from/source/local, to/target/neighbour, the port/interface on each end, speed and label. Drawbridge's own CSV inventory export uses these names, so it imports straight back in.

A NetBox device export lands without renaming anything. Its own column names are read as what they mean there: device_type is the model, not the shape; primary_ip4 (or primary_ip6) is the management address, with the /24 taken off, because the field holds an address rather than a prefix; site, location and rack join into the one Location field, outermost first, so a device arrives as DC1 / Hall 2 / R12; and asset_tag becomes an asset ACME-00912 clause in the notes rather than being dropped. A role column becomes the device's role band when it names one Drawbridge knows (core, access, spine, edge, ISFW…) and is otherwise ignored — somebody else's role vocabulary is not painted onto a tile as a code. A CSV naming just one of site/location/rack behaves exactly as it always did, and the NetBox devices CSV this app exports imports straight back in.

DOT files can carry the same data as attributes — kind= (a shape name from the palette), label=, mgmtIp=, serial=, model=, location=, notes= on devices; speed=, label= and taillabel=/headlabel= (or device:port endpoints) on links. They are ordinary DOT attributes, so a file written this way still renders in any Graphviz tool, and one written for Graphviz still imports here.

An import can also arrive as a link: a URL of the form …/app/#import=pako:… opens the app with this paste box pre-filled — same summary, same report, and nothing lands on the page until you press Import. The text travels in the URL fragment, which browsers never send to any server, so the diagram stays between whoever built the link and whoever opens it. scripts/make-import-link.mjs in the repository turns a file, or piped CLI output, into such a link.

An import merges into the devices already on the page. A pasted device whose name matches one that is already drawn — trimmed, and whatever the capitals — is that device: no second tile is created, its cables attach to the tile you already have, and asset fields that are still empty are filled in from the paste. Nothing you already have is overwritten, including the icon you chose and the name as you spelled it, and a cable that is already drawn between the same two devices on the same ports is not drawn again. So pasting the output of a second switch, which names the first one, adds only what is new. The rest of the paste — the genuinely new devices — is laid out and placed below the existing drawing as usual.

Which vendors are confirmed

Every neighbour-table form above was verified against real captures, not written from documentation. "Confirmed" means the capture imports with every neighbour drawn and nothing reported that should not be. The captures come from the ntc-templates test corpus (Apache-2.0) unless another source is named.

Vendor / OS Command Verified against
Cisco IOS / IOS-XE show cdp neighbors, show cdp neighbors detail 6 captures
Cisco IOS / IOS-XE show lldp neighbors, show lldp neighbors detail 13 captures
Cisco NX-OS show cdp neighbors, show cdp neighbors detail 6 captures; the summary table's Device-ID spelling since v0.88
Cisco NX-OS show lldp neighbors, show lldp neighbors detail 4 captures
Cisco IOS-XR show cdp neighbors detail, show lldp neighbors, show lldp neighbors detail 3 captures
Juniper Junos show lldp neighbors 2 captures
Arista EOS show lldp neighbors, show lldp neighbors detail 3 captures (the detail one 658 lines, 13 neighbours)
Aruba AOS-CX show lldp neighbor-info, show lldp neighbor-info detail 2 captures plus the HPE 10.13 guide
ArubaOS-Switch / ProCurve show lldp info remote-device, table and per-port detail 3 captures plus the AOS-S 16.11 knowledge base
HPE Comware / H3C display lldp neighbor-information list, … verbose 4 captures
Huawei VRP display lldp neighbor brief, display lldp neighbor 7 captures, and the brief sample a user sent in
Palo Alto PAN-OS show lldp neighbors all 4 captures
MikroTik RouterOS /ip neighbor print detail 3 captures
FortiOS diagnose lldp rx neighbor a FortiGate's own output
FortiSwitch (via FortiGate) diagnose switch-controller switch-info lldp neighbors-summary Fortinet KB 227554, column drift included

Not confirmed, and refused rather than guessed at: Extreme EXOS, Dell OS10, Brocade/Ruckus ICX detail, Allied Telesis. A capture of any of them sent through the feedback form is what it takes.

An HA pair, from the firewall itself

get system ha status on a FortiGate is the one command that says what no neighbour table can: that these two boxes are one cluster. Paste it whole — the health line, the usage blocks and the vcluster listing at the end are all read past — and Drawbridge draws the pair.

Both members arrive as firewalls carrying the cluster's model and their own serial number, and where the output says the cluster is A-P they get the role bands PRIMARY and STANDBY. Each heartbeat interface the output names — ha1, ha2, port5, whether from the HBDEV stats block or an hbdev list — becomes an HA cable between them, labelled with the interface at both ends. Monitored interfaces are not heartbeat interfaces and are never cabled.

The preview says what will happen before you press Import: "HA pair FGT-A / FGT-B, 2 heartbeat cables; 1 already on this page". A member that is already drawn is matched by its hostname as usual — and, uniquely to this format, by its serial number, so a firewall you drew as FW-EDGE-01 is recognised as the FGT-A in the paste rather than drawn a second time. When that happens the report says so. A role band you have already chosen is never overwritten; that too is reported rather than done silently. An active-active cluster has no standby, so no role band is set at all, and the report says why.

Master/Slave, as older FortiOS versions spell it, reads exactly the same. Half a cluster — a paste cut off before the second member — imports nothing and says which member did arrive: an HA pair is two.

The summary line says which it is before you commit: "8 devices — 3 already on this page, 5 new — 12 links (2 already drawn)". If a device really should be a second box, rename one of them before importing. The whole import, new tiles and filled-in fields together, is a single undo.

A name with its domain and the same name without it are one device: CDP and LLDP report a neighbour as sw-acc-07.corp.example.com while the switch's own prompt says sw-acc-07, and pasting from both ends of that cable draws one switch, not two. The tile keeps whichever spelling drew it first. An IP address is never read as a name with a domain, and if two devices on the page share a hostname under different domains a bare name matches neither and is drawn new rather than guessed.

Two names are never trusted to identify a device: Local device, which is what a paste that never says which switch it came from calls that switch, and a name the switch cut short — ArubaOS-Switch prints system names in 18 characters, so sw-harbour-dd01-it and sw-harbour-dd02 both arrive as sw-harbour-dd.... Those are matched by their cables instead: a tile is the same device when it already carries the same cable, on the same two ports, to a device that is already drawn. So the second of two switches' outputs joins the first rather than being hung off its hub, and an uplink both switches report is drawn once, to the right switch. When the paste knows a whole name for a tile drawn as Local device or …dd..., the tile takes it. The switch's name is read from its prompt (sw1# show lldp …) or, for output a script collected over SSH, from a # Hostname : sw1 line above it; a paste holding several switches' captures one after another is read as that many switches. For whole names from ArubaOS, add show lldp info remote-device <ports> — its detail blocks print them in full, and the table rows take them by chassis id.

Paste every capture at once and the captures name each other. A whole site's show lldp info remote-device, one capture per switch concatenated into one file, is read as one network: every row that prints the same chassis id is the same switch, however many switches list it, and a switch that the others print cut short takes its whole name from its OWN capture — matched by a cable both sides state, never by the name alone. An access switch with two uplinks is one tile with two cables, wherever its capture sits in the paste. A cut-short name that no capture explains — an access point, a switch nobody captured — keeps the name it was printed with, and one line in the report counts both: how many names were completed from the captures, and how many stay cut short. Two switches that print EACH OTHER cut short and are tied to nothing else in the paste stay cut short too: which is which would be a guess, and a stack of -1/-2 switches repeats the same ports everywhere. Add show lldp info local-device to the capture and even that pair is certain. One capture at a time works as well: every switch an import draws keeps its chassis id in its Chassis ID field, so a later paste that prints the same chassis — however cut short the name beside it — lands on that tile rather than drawing another, and the switch's own capture then names it whole. The one case that still needs the captures together is the pair above, two switches that print EACH OTHER cut short, pasted before the switch they both uplink to.

Where the paste and the page disagree, you are told. Filling in a field that is empty is safe; changing one you have already written is not — so when both sides state a value and the two do not match, the preview counts them instead of quietly keeping one: "7 devices differ: 4 serial, 3 model — 2 devices on this page are not in the paste". Tick Update differing fields and the import overwrites exactly those asset fields with the pasted values. It never changes anything else: not the name as you spelled it, not the icon you chose, not where a tile sits, and not a cable. The box is empty every time the dialog opens, because which of the two records is right is a judgement about this paste rather than a setting — and if you tick it and import the same output again, there is nothing left to differ.

The devices at the other end of that line — the ones on this page the paste never mentions — are listed by name, and that is all. A command run on one switch says nothing whatever about the switch in the next rack, so nothing is deleted, moved or greyed out on the strength of it; the list is there so you can see what this paste did not cover.

A port-channel summary binds cables instead of drawing them. A neighbour table tells you which cables exist; it never tells you which of them are one port-channel. So paste show etherchannel summary (or show port-channel summary on NX-OS) after you have imported the cabling, and every member port it lists that is drawn on this page goes into one aggregate ring, captioned with the channel — Po1, Po2. Nothing is ever created: a row naming ports you have not drawn is reported, and the ring is bound over the ones you have. Ports match however they are spelled — Gi1/0/1 and GigabitEthernet1/0/1 are the same port.

Drawbridge has to know which switch the output came from. If you pasted the command line with it — SW-CORE-01#show etherchannel summary — that is the answer; otherwise select the switch on the canvas first, and it will use that. With neither, it says so rather than guessing, because binding another switch's cables into a ring would be a wrong drawing that looks right. The summary line reads "2 port-channels bind 4 cables; 1 port not on this page" before you commit, the whole paste is a single undo, and pasting the same output a second time does nothing at all — a channel that has since gained a member extends the ring it already made rather than drawing a second one on top.

A Containerlab topology arrives as the lab it describes. Paste a *.clab.yml and every node under topology.nodes becomes a device: the NOS kind picks the icon, mgmt-ipv4 becomes the management address, the container image becomes the model, and group becomes a zone around its members — drawn clear of the other groups' boxes, giving up a little of its padding where the lab's own shape leaves two rows close together. The whole Containerlab catalogue is read — the modern vendor_os spellings and the older vr-* aliases beside them — and each is mapped to what the platform is actually used as rather than by its prefix: nokia_srlinux, arista_ceos, cisco_n9kv and vr-vqfx are L3 switches, nokia_sros, juniper_vmx and cisco_xrv9k are routers, fortinet_fortigate, paloalto_panos, vr-vsrx and vr-ftdv are firewalls, bridge is a switch and linux is a server. A vr-* image nobody has listed yet falls back to a router, so a brand-new alias still lands as something sensible. Inheritance is honoured the way Containerlab does it — node, then group, then kinds, then defaults — so a topology whose nodes are bare still arrives as the right hardware. Links are read in both spellings, the brief endpoints: ["srl:e1-1", "ceos:eth1"] and the extended form with a mapping per end, and the interface names are kept exactly as written: e1-1 is what the lab calls that port, and rewriting it to Ethernet1/1 would make your drawing disagree with your file. A kind nobody recognises still arrives, as a neutral tile, with a line saying which kind that was; a host: or mgmt-net: endpoint is lab plumbing rather than a device, so it is reported instead of being invented as a box.

Meraki and UniFi arrive as files, not as connections. Both are exports you download from your own dashboard or controller and then open here — Drawbridge never asks for a controller address, never asks for an API key and never makes a request of its own. A Meraki link-layer topology becomes its devices (the model decides the icon: MS is a switch, MX a firewall, MR an access point, MV a camera) with model, serial and address filled in, and its links become cables labelled with the port each end heard the other on; a device the dashboard does not manage still appears, named from what LLDP said, with a line telling you so. A UniFi device export becomes its switches, gateways and access points, and its cabling comes from both the lldp_table and the uplink of every device — the same wire seen from both ends is folded into one cable, an LLDP neighbour that is not itself in the export is drawn as a neutral tile, and a device you have never named in the controller is drawn by its MAC and says as much. A UniFi export states cabling and nothing about position, so it is drawn in the three-layer model like any other neighbour output: the gateway on the top row, the switches below it by what their names say, and the access points in a block under the switch they plug into.

Mermaid comes in the way it was written. Paste a ```mermaid block straight out of a README, a GitLab wiki, an Obsidian note or a chat with an AI — fence and all, front matter and %%{init} and classDefs and all — and the drawing arrives. Every node shape is read, subgraph becomes a zone holding whatever is declared inside it, nested ones nest, and a link's text becomes the cable's caption. Where a node says what it is — A@{ icon: "mdi:firewall" }, a cylinder for a datastore, service fw(mdi:firewall) in an architecture-beta diagram — it gets that icon; where it does not, it gets the neutral tile rather than a guess. Arrows decide the tiers, exactly as in a DOT paste: A --> B puts B a level below A, while A --- B and A <--> B say the two are peers and keep them side by side. An architecture-beta side port (db:R -- L:server) becomes the face the cable leaves from. Anything the parser does not understand is reported with its line number and the rest still imports; only a fence holding a diagram type Drawbridge does not read — a gantt, a sequenceDiagram — comes to nothing, and it says which.

Mermaid has nowhere to put a management IP, so Drawbridge writes its own facts into comment lines — %% fk: n1 kind=switch mgmtIp=10.0.0.1 — which every mermaid renderer on earth ignores and this importer reads. Copy Mermaid writes them too, which is what makes its own output come back whole: copy a page as Mermaid, paste it into an article, copy it out of the article a year later and paste it back here, and you get the same devices with the same icons, the same asset data, the same zones and the same captioned cables. You can write those lines by hand as well — see Diagrams from code.

An address plan is a drawing waiting to happen. Export your prefixes from NetBox, phpIPAM or whatever spreadsheet you actually keep them in, paste the CSV, and every row becomes a zone with the prefix in its Subnet field — a page of labelled, empty boxes you can then drag devices into. Nothing is invented: a prefix list names no devices and no cables, so none are drawn, and the preview says so in its own words — "Prefix list — subnets — 8 prefixes → 8 zones".

The header decides how it is read, not the column order, and a device-name column anywhere in it means the file is an inventory rather than an address plan and is read as one. prefix, cidr, subnet and network all name the prefix column; phpIPAM's split subnet + mask pair is joined back into a CIDR, dotted netmasks (255.255.254.0) included. The zone's title is the row's description, or its role when there is no description, with the VLAN folded in front of it: VLAN 120 · Servers. A row that says nothing but its prefix is titled with the prefix. IPv6 prefixes are drawn like any other, with one difference worth knowing: the "IP outside this zone's subnet" hint is IPv4-only, so an IPv6 subnet is a label rather than a check. A row whose prefix is not a prefix is reported with its line number and the rest still lands.

Your Ansible inventory already knows the shape of the network. It names every box and files each one under a group, which is most of a drawing — so paste it, in either spelling, and you get the devices inside the zones their groups describe. ansible_host becomes the management address, and a host named by its address alone becomes both. Groups nest as they do in the file: [datacenter:children] holding core and access draws one zone with two zones inside it. The preview counts it as "12 hosts in 3 groups".

The icon comes from a drawbridge_kind (or plain kind) host variable when you set one — fw-01 ansible_host=10.0.99.1 drawbridge_kind=firewall — and otherwise from the name, exactly as a pasted neighbour table decides it. all and ungrouped never become zones: they are Ansible's own bookkeeping, and a box around the whole drawing says nothing. A host filed under two groups is drawn in the first, and the report says which group it also belongs to, because a tile in this app is inside exactly one box. A host range (web[01:03]) is reported rather than expanded — writing three names the file never contained would be inventing devices. The YAML form is read by Drawbridge's own small YAML reader, so an inventory using anchors or block scalars still imports, with those lines reported.

Your port descriptions are a cabling record, and Drawbridge can read it. Paste show interfaces description and every description that NAMES a device already on the page becomes a cable from that port to that device. It recognises the ways people actually write them: to SW-ACC-07 Gi1/0/49, uplink CORE-01, -> FW-A port5, LEAF-03:et1/1, and the bare SW-ACC-07 Gi1/0/49. Where the description writes an interface after the name, the far end of the cable is labelled with it; where it stops at the name, the cable is drawn without one, because inventing an end label is inventing a fact. IOS, IOS-XE, NX-OS and EOS all print this differently and all three are read, second and third tables included.

It never creates a device. A description is prose, and a name is only a device when that device is already drawn — matched whole, trimmed and whatever the capitals, exactly as an import matches names anywhere else. Uplink, Server, reserved, a switch that belongs to somebody else's site: every one of them is listed in the report with its line number and drawn as nothing. The match is deliberately strict about this — a page holding SW-1 and a port described as to SW-10 draws no cable at all, because the wrong cable is the one failure here you would never notice: the drawing looks perfectly reasonable and says something untrue.

The summary line counts it before you commit — "12 descriptions: 5 cables to devices on this page (2 already), 7 named nothing here" — and, as with a port-channel summary, Drawbridge has to know which switch the output came from: the command line pasted with it (SW-CORE-01#show interfaces description), or the switch selected on the canvas, or it says so rather than guessing. Ports with nothing written on them are counted in one line rather than reported one by one. The whole paste is a single undo, and pasting the same output again does nothing: a cable already drawn between the same two devices on the same port is the cable the description is talking about, however either end spells the port.

The dialog shows, before anything is committed:

Good rows are imported and bad ones are reported — real pasted output always contains legends, prompts and footers. Import stays disabled until the paste yields something to do: at least one device, or — for a port-channel summary, which creates nothing — at least one ring it can bind.

Dropping in images

Drag an image file onto the canvas, or paste one from the clipboard, and it becomes an image node at the drop point.

Inventory

Export → CSV inventory writes every device on every page as <title>-inventory.csv: page, label, kind, role, functions, the asset fields — Management IP, Serial, Model, Location, Notes, Link — then where it stands: Rack, U and Zone, then Installed, Power, and last Chassis ID (appended, like each column after Link, so a spreadsheet built on an older export keeps its columns where they were). The rack and the U are only filled for a device actually mounted in a rack, and the U is counted from the bottom, as racks are numbered; the zone is the innermost zone or virtual context the device sits inside, so a VDOM drawn inside a site reports the VDOM. Installed reads yes for a device you have ticked as installed and no for one you have marked planned; a device nobody has said anything about leaves it empty, so the column counts what is really there rather than guessing.

Two things are worth knowing about the last columns. Location is inherited: a unit mounted in a rack that carries a Location reports the rack's, so documenting a full rack is one field rather than forty — a unit's own Location wins wherever it has one. Power reads A, B or AB for a rack unit you have put on a feed, and is empty for everything else; empty means nothing was recorded, which is not the same claim as "unpowered".

Devices with no asset data are listed too — the point is to show what is still undocumented. The file is written with a UTF-8 byte-order mark so Excel reads non-ASCII names correctly, and every cell is guarded so a value beginning with =, +, - or @ cannot be executed as a spreadsheet formula. If the document has no devices at all, nothing is written and you are told so.

Tables

The page as rows: Devices, Cables, Optics, Zones and Racks, each tab with its count — and Channels on a page that has optical kit on it. In Studio, press Tables at the top of the window; Draw brings the drawing back. In Classic, switch Table on in Edit layout and it docks along the bottom of the drawing.

The columns are the exports' own — the inventory's on Devices, the cable schedule's on Cables, the optics report's on Optics — and Columns chooses which ones show, per tab; the choice is remembered. Cells you can change look like text until you point at them: click one and type. Enter keeps it and moves down the column, Tab moves right, Esc puts it back. Each cell is one ⌘Z (Ctrl+Z), and the drawing follows at once — a serial typed here is the device's serial, in its Facts panel and in every export. A table edit is exactly the edit the Facts panel makes; nothing is stored anywhere else.

What the properties panel picks from a list, the table does too: a device's Kind, and a cable's Type (Normal, Trunk, LAG…), Speed and Cable are lists with the panel's own choices, and a device's Role suggests the roles its kind has while still taking anything you type. A pasted value may use either the stored code or its name — trunk or Trunk (802.1Q).

A row's checkbox is the drawing's selection: tick rows to select them, and what you select on the drawing is ticked here.

Adding a cable by hand

On the Cables tab, Add cable… opens a form for a cable you would rather type than draw. Choose the A device and the B device from the devices on the page, give each end its port, and fill in whatever you know — Speed, Cable type, Link type, Length, Label, VLAN. Everything but the two devices can stay empty. Add cable (or Enter in any field) draws it: the cable is routed on the drawing, selected, and is a new row in the table. It is one ⌘Z (Ctrl+Z), ports and all.

The form stays open for the next one. The A device stays chosen, because cables typed in a row usually leave the same switch, and the speed, cable type and length stay too; the B device, the ports and the label are cleared. Done puts the form away.

Devices you have not named yet. A device you have just placed is labelled with its shape's name, so two new firewalls would both read Firewall. The table numbers them instead, in the order you read the page — rows from the top, each from the left — as Firewall 1 and Firewall 2, followed by the model or management IP when the device has one. On the Cables tab that name is grey, because it is not a name yet; on the Devices tab it is the grey hint in the empty Label field, and typing there names the device. In Add cable… these devices come after the named ones, marked (unnamed), and choosing one selects it on the drawing so you can see which it is. A label that two devices share is numbered the same way.

Channels

On a page with optical kit — an optical shelf or a cable set to DWDM or CWDM — the tab strip gains a Channels tab. (On every other drawing it is not there: a tab that can only be empty is in the way.) Like the shelf's own plan, every row is read from the cables; nothing on this tab can be typed in.

It opens By pair: one section per fibre between two sites, headed DWDM-A ⇄ DWDM-B with the fibre's label and length and how many of the grid's channels it carries — 2 of 45, the 100 GHz C-band grid, or of 18 for a CWDM fibre. Under it, one row per channel: the channel, what it is in nanometres and terahertz, and then what each END does with it — the client SW-1 xe-0/0/1 where the channel lands, or through → DWDM-C where it passes that site and carries on. A channel lit on three fibres is in three sections, because that is three fibres carrying it. Two devices joined by WDM cables with no shelf drawn round them are a pair too, listed the same way with each end's port.

End to end is the same page the other way round: one row per channel, client to client, following the through channels across every site it crosses — DWDM-A → DWDM-B (through) → DWDM-C — with the spans' lengths added up (42 km + 18 km) when every one of them says how long it is. A channel that lands at only one end reads — with a quiet one end only: that is a drop site's normal case, not a problem. Which view you last used is remembered on this device.

Show free channels adds, to each section in By pair, one muted last row listing the grid's unused channels as chips — what is left to light on that fibre. Export CSV writes whichever of the two tables you are looking at.

Clicking a row shows that channel's cables on the drawing: the spans it crosses, each shelf's own cable for it, and the clients at its ends. And a channel that is on one fibre twice is tinted, says twice on this span, and offers Checks, where the same thing is a sentence with its fix.

Paste rows fills in rows from a spreadsheet: copy them with their header row, paste, and press Fill in. Columns are matched by their headers the way the CSV import matches them — Hostname, SN and IP all work — and rows are matched to what is on the page by name. A paste fills in; it does not draw new devices, and an empty cell clears nothing. The notice says how many rows were filled in, which names are not on the page, and which columns were ignored. Reconcile with import opens the import dialog, for bringing the page in line with a list. Export CSV and NetBox CSV are the Export menu's own files for that tab.

When the file changes underneath you

The desktop app watches the file you have open. If something else changes it — an assistant driving the MCP tools, draw.io in another window, a git pull or a branch switch — you do not have to reopen it.

What happens next depends on whether you have unsaved work:

Two things it deliberately does not do. It never reacts to your own saves, so working normally is silent. And it waits for the file to stop changing before reading it — a program part-way through writing a diagram is briefly an empty file, and reloading that would be worse than waiting a moment.

This is the desktop app only. The browser has no way to watch a file it was handed.

Revisions

Revisions in the toolbar's File group saves the drawing as it stands and lets you load it back later. Each saved revision gets an identity of its own: a number that never repeats and a stamp to the minute, so a row reads #4 · 2026-08-04 14:32. Type a note as you save it and the row says what changed. The document's own revision label and your initials ride alongside as context — several snapshots can share those, because the document only gets a new revision when somebody issues one.

They live inside the .drawio file. Send somebody the drawing and its history goes with it — there is no sidecar file to lose and nothing to keep in step. Snapshots are compressed, and the dialog shows what the history costs the file so you can prune it before it matters. Twenty revisions are kept; past that the oldest is dropped and the app says so.

An embedded picture — a logo in the title block, a pasted screenshot, a floor plan behind the drawing — is stored once for the whole file, however many revisions use it, and the copy goes when the last revision using it does. So saving a revision of a drawing with pictures in it costs the drawing, not the pictures again.

Each row offers three things:

Compare with file…, at the top of the dialog, compares the drawing with another .drawio on disk instead of with one of its own revisions — last month's copy on the share, the version a colleague sent back, the file as it was before the change window. Pick the file and the same read-only Compare mode opens, with the panel titled after it: the file you picked is the baseline, so "added" means present in the drawing now and absent from that file. The picked file is read once and forgotten — no handle, no path, nothing is ever written back to it — and it is offered even when this drawing has no revisions of its own. Visio files are not offered here: comparing across formats would fold the Visio importer's fidelity into every difference. Cancelling leaves the dialog where it was.

Only the .drawio carries the history. A Visio (.vsdx) save or export has nowhere to put it and takes only the current drawing. Save As .vsdx says so when it happens, because from then on the document saves without its history and cannot take a new revision until it is saved as .drawio again; an export is a copy, leaves the history where it was, and says nothing — like every picture export (SVG, PNG, PDF), which is of course just the drawing. Opening the file in draw.io works normally; draw.io ignores what it does not recognise, but saving there drops the history, because draw.io rewrites the file from its own model.