Writing diagrams
How to write flow, sequence, and schema diagrams in the diagram language, with a pointer to the guide of each of the seventeen other kinds. There are twenty kinds in all. It is for anyone, human or AI agent, who writes diagram source. Every example on this page is a real diagram, rendered by the tool this guide teaches, so you can copy one and it works.
TL;DR
| Rule | Yes | No |
|---|---|---|
| Statements | One per line | Two statements on one line |
| Labels | Quoted: "Auth API" | Bare words with spaces |
| Meaning | A node type (database) or a tone= (violet) | A color value such as #7c3aed; colors are not accepted |
| Ids | Short and stable: api, user_db, as-jwks | Spaces, or an id reused for two things |
| Icons | icon=lucide:key, icon=simple:postgresql, icon=aws:lambda, icon=status:success | A made-up name; it is rejected with suggestions |
| Groups | A name and a type for what the group is: group vpc "Application VPC" type=network { | A color, or a long sentence as the name |
| Size | About 15 nodes, 6 tables, or 6 actors at most | One diagram that tries to show everything |
| Labels | Short, in sentence case | A paragraph inside a node |
| Step numbers | numbered=on in the header, step=off on a line to skip one | A number typed into each label |
1. How a diagram starts
The first line names the kind, gives a quoted title, and may add options. The title becomes the accessible name of the picture.
| Kind | Header | Options |
|---|---|---|
| Flow | flow "Title" | direction= (auto, right, down, left, up), desc="Summary", animate=on, numbered=on, start= |
| Sequence | sequence "Title" | desc="Summary", animate=on, numbered=on, start= |
| Schema | schema "Title" | desc="Summary" |
| Git graph | git "Title" | direction=, main=, desc=. See Git graphs. |
| Gantt chart | gantt "Title" | start=, unit=, today=, weekends=, holidays=, desc=. See Gantt charts. |
| Class diagram | class "Title" | direction=, desc=. See Class diagrams. |
| State diagram | state "Title" | direction=, desc=. See State diagrams. |
| Timeline | timeline "Title" | direction=, side=, desc=. See Timelines. |
| User journey | journey "Title" | marks=, legend=, desc=. See User journeys. |
| Mind map | mindmap "Title" | layout=, desc=. See Mind maps. |
desc is a one- or two-sentence summary. It is used as the picture's alt text, so write what the diagram says. animate, numbered, and start are explained in Numbered steps and Animated lines.
Lines that start with # are comments.
2. Flow diagrams
A flow diagram shows things and the connections between them. Use it for architecture and for processes.
| Statement | Form | Notes |
|---|---|---|
| Node | node <id> <type> "Label" sub="Detail" tone=blue icon=lucide:key stack=simple:go | sub, tone, icon, and stack are optional. stack= adds up to three technology marks under the text. |
| Group | group <id> "Name" type=network provider=aws {, then nodes, then } | type, provider, icon, and tone are optional. Groups nest. type= says what the group is and sets its icon, tone, and frame style. |
| Edge | a -> b : "label" icon=status:yes role=data animate=off step=12 | The label, the icon, role=, animate=, and step= are optional. An icon needs a label. A label that names a relationship with a verb, such as reads or publishes OrderPlaced, draws its icon by itself. |
Arrows:
| Token | Meaning |
|---|---|
-> | Solid line, arrowhead at the end |
--> | Dashed line, arrowhead at the end |
..> | Dotted line, arrowhead at the end: an asynchronous call such as a webhook or an event |
<-> and <--> | Arrowheads at both ends |
-- | A line with no arrowhead |
Where one line crosses another at a right angle, the horizontal one hops over the other with a small arc, so a reader sees a crossing and not a join, in solid and dashed lines alike. Lines that meet or share a stretch are not crossings and do not hop. A bend right beside a hop tightens a little to make room, and a hop is left out where it would still land on a bend, an arrowhead, a node, or a label. There is nothing to write for it.
A node's type sets its default icon, tone, and look. The flow node types page lists every type. These are the most used:
| Type | Use for |
|---|---|
person, agent, client | Who or what starts things |
service, gateway, function | Code that runs |
database, cache, queue, storage | Where data lives |
identity, secret | Trust and credentials |
external | Systems you do not own (dashed outline) |
step, decision, terminal | Process steps, branches, and end states |
A group draws a frame around its nodes and carries a corner tab: a small box fixed to the frame's top-left corner that holds the group's icon and its name on one line. The tab grows to fit the name, with the same padding on both sides. A name that is longer than the widest node card is cut with an ellipsis and warning W203, so keep a group's name to a few words and put detail in the nodes.
| Group option | Effect |
|---|---|
type=<type> | What the group is, from a closed list such as account, region, network, subnet, data-tier, or trust-boundary. It sets the icon, the tone, and a solid, dashed, or dotted frame. |
provider=<provider> | Whose resources the group holds (aws, azure, gcp), so a type draws that provider's icon. Nested groups inherit it. |
icon=<set>:<name> | Overrides the icon in the tab. A group with no type has none unless you set one. |
tone=<tone> | Overrides the tone. Without a type the group is neutral. |
sub="..." | A short second line in the tab under the name, such as an address range. |
stack=<set>:<name>,... | Up to three small marks in the tab, written like the marks of a node. |
icon=none | Removes the icon. |
type=none | Keeps the group plain: no type from its name and no suggestion. |
A group with no type= whose name gives its type for certain takes it by itself: VPC, Production, CI pipeline, Kafka, Kubernetes, us-east-1, Data, Security, and Monitoring are typed, while Data science team and Data pipeline stay plain. A name that only suggests a type gets warning W205 with the type= to add. icons=off on the header turns this off along with the automatic icons.
An overlay draws a dashed frame around nodes or groups it does not own, wherever layout put them. It is one line after the members, overlay set "Web machines" type=scale-group members=w1,w2,w3, and it takes type=, provider=, icon=, tone=, sub=, and stack= like a group. Use it for a scaling group that spans two zones or a redundant set of machines inside a tier. When the members sit in different groups, the frame crosses the group frames on purpose, and warning W206 says if it would also surround a node that is not a member.
A group nested in another is shaded one step stronger than the one around it, in light and in dark. The group types page draws every group type and the icon each draws for each provider, the cloud architecture guide has the recipe and the icon pages for every cloud product, and the architecture patterns guide has ready-made diagrams for the common shapes.
An edge that would pass under a tab is moved to the side when the nodes it joins allow it, and otherwise passes behind the tab.
flow "Nested groups and directions" desc="A request crosses a trust boundary into a private network containing an API and its data stores." direction=down
node client person "Client"
group edge "Public edge" type=edge { node gw gateway "API gateway" sub="TLS and rate limits"}
group internal "Private network" type=trust-boundary tone=violet { node api service "Orders API" group data "Data stores" icon=lucide:database { node db database "Orders DB" node cache cache "Session cache" }}
client -> gw : "HTTPS"gw -> api : "forward"api -> db : "read and write" role=dataapi --> cache : "lookup"Nodes:
- Client, person
- Edge: Public edge
- API gateway (TLS and rate limits), gateway
- Trust boundary: Private network
- Orders API, service
- Data tier: Data stores
- Orders DB, database
- Session cache, cache
Connections:
- Client to API gateway: HTTPS
- API gateway to Orders API: forward
- Orders API to Orders DB (stored data line): read and write
- Orders API to Session cache (dashed): lookup
Make it look finished
Four habits make a flow diagram look finished without a single colour value:
| Habit | Write | What it draws |
|---|---|---|
| A verb on each arrow | api -> db : "reads orders" | The relationship's icon in the label: an eye for reads, a pen for writes, a paper plane for publishes, a rocket for deploys |
| A role on the lines that mean something | role=data, role=async, role=identity, role=private | The line in the role's colour, named in a legend. Warning W500 lists the arrows to mark |
| A type on each group | type=network, or a name such as "Production" | The frame, tone, and icon of the kind |
| Product names on nodes | "Postgres", "Redis", "Amazon S3" | The product's mark |
W204, W205, and W500 are style suggestions: validate --strict and the Action's fail-on: warning pass them.
flow "Checkout" direction=right desc="A web app calls the checkout API, which writes orders to Postgres, reads sessions from Redis, and publishes an event that an email worker consumes."
node web client "Web app"node api service "Checkout API"
group data "Data" { node db database "Postgres" node cache cache "Redis"}
group events "Event bus" { node bus queue "Kafka"}
node mailer service "Email worker"
web -> api : "calls"api -> db : "writes orders" role=dataapi -> cache : "reads sessions" role=dataapi -> bus : "publishes OrderPlaced" role=asyncbus -> mailer : "consumes" role=asyncNodes:
- Web app, client
- Checkout API, service
- Email worker, service
- Data tier: Data
- Postgres, database
- Redis, cache
- Messaging: Event bus
- Kafka, queue
Connections:
- Web app to Checkout API: calls
- Checkout API to Postgres (stored data line): writes orders
- Checkout API to Redis (stored data line): reads sessions
- Checkout API to Kafka (asynchronous line): publishes OrderPlaced
- Kafka to Email worker (asynchronous line): consumes
3. Sequence diagrams
A sequence diagram shows messages between actors over time. Use it for protocols, login flows, and code paths.
| Statement | Form | Notes |
|---|---|---|
| Actor | actor <id> <type> "Label" sub="Detail" icon=lucide:key tone=blue | Declared in column order, left to right. |
| Message | a -> b : "label" animate=off step=off | Use --> for a reply. a -> a is a message to itself. animate= and step= are optional. |
| Note | note over a : "text" or note over a, b : "text" | The second form spans several actors. |
| Frame | alt "condition" {, messages, } else "other" {, messages, } | Also opt, loop, and par (with and for the next branch). |
A message between two actors that are not neighbours crosses the lifelines of the actors between them. Each crossing is drawn as a small arc that the arrow hops over, so a reader sees a crossing and not a join, in solid and dashed arrows alike. There is nothing to write for it.
sequence "Checkout with retries" desc="A client pays for an order. The payment is retried up to three times, and inventory is updated in parallel with the receipt."
actor app client "Shop app"actor pay service "Payments"actor inv service "Inventory"actor mail service "Email"
app -> pay : "Charge order"
loop "up to 3 attempts" { pay -> pay : "Call card network" pay --> app : "Result"}
note over app, pay : "A declined card stops the flow after the third attempt"
opt "payment accepted" { par "update stock" { pay -> inv : "Reserve items" } and "send receipt" { pay -> mail : "Receipt" }}Participants:
- Shop app, client
- Payments, service
- Inventory, service
- Email, service
Steps:
-
- Shop app to Payments: Charge order
- loop: up to 3 attempts
- 2. Payments to Payments: Call card network
-
- Payments to Shop app (dashed): Result
- Note over Shop app and Payments: A declined card stops the flow after the third attempt
- opt: payment accepted
- par: update stock
- 4. Payments to Inventory: Reserve items
- and: send receipt
- 5. Payments to Email: Receipt
- par: update stock
4. Schema diagrams
A schema diagram shows database tables, their columns, and the relations between them.
| Statement | Form | Notes |
|---|---|---|
| Table | table <name> sub="Detail" icon=lucide:table tone=aqua {, then columns, then } | sub, icon, and tone are optional. |
| Column | <name> <type> pk fk unique nullable note="Why" | Flags and note are optional and may be combined. |
| Relation | child.column -> parent.column : "label" | Written from the referencing column to the referenced one. The label is optional; leaving it out keeps wide schemas narrower. |
A relation draws a crow's foot at the many end. It draws a single tick at the one end, and at both ends when the referencing column is unique.
schema "Orders" desc="Orders belong to customers; each order has one invoice and many items."
table customers sub="People who buy" { id uuid pk email text unique}
table orders { id uuid pk customer_id uuid fk placed_at timestamptz note text nullable}
table invoices { id uuid pk order_id uuid fk unique note="One invoice per order"}
orders.customer_id -> customers.idinvoices.order_id -> orders.idTables:
- customers
- id uuid (primary key)
- email text (unique)
- orders
- id uuid (primary key)
- customer_id uuid (foreign key)
- placed_at timestamptz
- note text (nullable)
- invoices
- id uuid (primary key)
- order_id uuid (foreign key, unique): One invoice per order
Relations:
- orders.customer_id references customers.id (many to one)
- invoices.order_id references orders.id (one to one)
5. Icons
Every node, actor, table, and group can carry one icon, set with icon=<set>:<name>. A group shows its icon in its corner tab.
| Set | Contents | Example |
|---|---|---|
lucide | General icons | icon=lucide:key-round |
simple | Technology and brand logos | icon=simple:postgresql |
status | Colored outcome icons | icon=status:success |
aws, gcp, azure, alibaba, oci, ibm, cloudflare, digitalocean, vercel | A provider and its products | icon=aws:lambda, icon=gcp:bigquery, icon=azure:functions, icon=alibaba:oss |
Find a name with the search_icons tool, or ask your agent to choose icons for you. Search matches ideas as well as names: token exchange, validate, revoke, timeout, and bug each find suitable icons. It also knows products: lambda, s3, gke, cosmos db, bigquery, and serverless function find the right provider icon first. The icon reference shows every icon. A name that does not exist is an error with a suggestion, so a typo never renders as a blank space, and a guessed provider such as amazon:s3 is pointed at aws:s3.
Status icons keep their own color on any node: green for good, red for bad, amber for caution, blue for information, and neutral for waiting. The full list, with meanings, is in the language reference.
flow "Deployment pipeline" desc="A change is built, tested, and deployed. A failed test stops the pipeline." direction=down
node build step "Build" icon=status:successnode test step "Test" icon=status:error sub="2 failures"node deploy step "Deploy" icon=status:skippednode notify step "Notify team" icon=status:warning
build -> testtest -> deploy : "blocked"test --> notify : "failed"Nodes:
- Build, step
- Test (2 failures), step
- Deploy, step
- Notify team, step
Connections:
- Build to Test
- Test to Deploy: blocked
- Test to Notify team (dashed): failed
Cloud architecture
Name a product with its provider and its own name: aws:lambda, gcp:pubsub, azure:entra-id. Give the group that holds a cloud's resources type=account and its provider=, which draws the provider's own icon (<provider>:cloud), and nest a type=region group for each region. The cloud architecture guide covers the whole recipe, and the icon pages (AWS, Azure, Google Cloud) draw every product. Where a provider publishes an official icon for a product, or has a licensed brand mark, the icon draws it; every other product draws the glyph for its kind of service, so a function looks like a function whichever cloud it runs on. The label says whose it is.
flow "Multi-cloud order platform" desc="Orders enter through AWS, are stored and queued in one region, replicated to Google Cloud for analytics, while customers sign in through Azure." direction=down
node users client "Customers" sub="Web and mobile"
group aws "AWS" type=account provider=aws tone=orange { node cdn gateway "CloudFront" icon=aws:cloudfront group eu "eu-west-1" type=region { node api gateway "API Gateway" icon=aws:api-gateway node fn function "Order handler" icon=aws:lambda sub="Validates and stores" node db database "Orders table" icon=aws:dynamodb node queue queue "Order events" icon=aws:sqs }}
group gcp "Google Cloud" type=account provider=gcp tone=blue { node feed queue "Event feed" icon=gcp:pubsub node analytics database "Analytics" icon=gcp:bigquery}
group azure "Azure" type=account provider=azure tone=violet { node sso identity "Sign-in" icon=azure:entra-id}
users -> cdn : "HTTPS"cdn -> apiapi -> fnfn -> dbfn -> queuequeue -> feed : "replicate"feed -> analyticsusers -> sso : "SSO"Nodes:
- Customers (Web and mobile), client
- Account: AWS
- CloudFront, gateway
- Region: eu-west-1
- API Gateway, gateway
- Order handler (Validates and stores), function
- Orders table, database
- Order events, queue
- Account: Google Cloud
- Event feed, queue
- Analytics, database
- Account: Azure
- Sign-in, identity
Connections:
- Customers to CloudFront: HTTPS
- CloudFront to API Gateway
- API Gateway to Order handler
- Order handler to Orders table
- Order handler to Order events
- Order events to Event feed: replicate
- Event feed to Analytics
- Customers to Sign-in: SSO
6. Decisions and outcomes
A decision node is a tinted card with a branch icon. Label the outgoing edges with the answers. A label that is exactly yes or true gets a green tick, and no or false gets a red cross, without any extra syntax. Add icon= to an edge for another icon, or icon=none to remove the automatic one. A terminal node is a pill, and it can carry a status icon for the outcome.
flow "Review outcome" desc="A pull request is approved or sent back for changes." direction=down
node pr step "Pull request opened"node review decision "Does the change pass review?"node merge terminal "Merged" icon=status:successnode changes terminal "Changes asked" icon=status:retry
pr -> reviewreview -> merge : "yes"review -> changes : "no"Nodes:
- Pull request opened, step
- Does the change pass review?, decision
- Merged, terminal
- Changes asked, terminal
Connections:
- Pull request opened to Does the change pass review?
- Does the change pass review? to Merged: yes
- Does the change pass review? to Changes asked: no
7. Numbered steps
Add numbered=on to the header of a flow or a sequence diagram and every line gets a numbered badge, counting in the order the lines are written. In a sequence diagram the badge sits on the message, and the messages inside alt, opt, loop, and par blocks are numbered in the order they are read. In a flow diagram it sits on the connection. Use it for a procedure, a protocol, or a runbook, where a reader says "step 4" aloud.
| Where | Option | What it does |
|---|---|---|
| Header | numbered=on | Numbers every line, starting at 1. |
| Header | start=5 | Starts at 5 instead. A whole number from 0 to 999, and it needs numbered=on. |
| One line | step=off | Draws no badge on this line, and uses up no number. |
| One line | step=12 | Gives this line the number 12. The lines after it continue from 13. |
A step= on a line of a diagram that is not numbered is an error, and so is a count that runs past 999. A sequence diagram:
sequence "Sign in with an emailed code" numbered=on desc="A user asks for a code, receives it by email, and trades it for a session. Each message carries its step number."
actor user person "User"actor web client "Web app"actor api service "Auth API"actor mail service "Email"
user -> web : "Enter email"web -> api : "Request code"api -> mail : "Send the code"mail --> user : "Email arrives"user -> web : "Type the code"web -> api : "Verify code"api --> web : "Session cookie"Participants:
- User, person
- Web app, client
- Auth API, service
- Email, service
Steps:
-
- User to Web app: Enter email
-
- Web app to Auth API: Request code
-
- Auth API to Email: Send the code
-
- Email to User (dashed): Email arrives
-
- User to Web app: Type the code
-
- Web app to Auth API: Verify code
-
- Auth API to Web app (dashed): Session cookie
A flow diagram, where the number sits on each connection:
flow "Release steps" numbered=on direction=down desc="A change is pushed, built, published as an image, and deployed. Each connection carries its step number."
node dev person "Developer"node git service "Git host"node ci service "CI"node reg storage "Registry"node prod service "Production"
dev -> git : "Push"git -> ci : "Trigger build"ci -> reg : "Publish image" icon=lucide:uploadci -> prod : "Deploy"Nodes:
- Developer, person
- Git host, service
- CI, service
- Registry, storage
- Production, service
Connections:
-
- Developer to Git host: Push
-
- Git host to CI: Trigger build
-
- CI to Registry: Publish image
-
- CI to Production: Deploy
8. Animated lines
Add animate=on to the header of a flow or a sequence diagram and every line that has an arrowhead gets moving dashes that travel toward the head. It shows which way data or control goes, which suits a pipeline or a request path. To animate one line only, write animate=on on that line. To keep one line still while the header animates the rest, write animate=off on it.
| Rule | What happens |
|---|---|
animate=on in the header | Every line with an arrowhead moves. A -- line has no arrowhead, so it stays still. |
animate=off on a line | That line stays still, whatever the header says. |
animate=on on a -- line | An error, because dashes travel toward an arrowhead and the line has none. Use ->, or remove the option. |
A line with two arrowheads (<->) | It moves from its first end to its second. |
The motion is part of the SVG, so it moves wherever a browser shows the SVG as an image, including a README on GitHub and this page. It stays still in a PNG and in a reader that flattens the SVG into a picture, and it is switched off when the reader asks for reduced motion in their system settings. The picture means the same thing without it, so never rely on motion to say something that the text does not.
flow "Events into the warehouse" animate=on direction=down desc="Events flow from an app through an API and a queue to a processor, which stores them. The acknowledgement is drawn still."
node app client "Mobile app"node api gateway "Events API"node queue queue "Event queue"node worker function "Processor"node db database "Warehouse"
app -> api : "POST /events"api -> queue : "enqueue" role=asyncqueue -> worker : "consume" role=asyncworker -> db : "store" role=dataworker --> api : "ack" animate=offNodes:
- Mobile app, client
- Events API, gateway
- Event queue, queue
- Processor, function
- Warehouse, database
Connections:
- Mobile app to Events API: POST /events
- Events API to Event queue (asynchronous line): enqueue
- Event queue to Processor (asynchronous line): consume
- Processor to Warehouse (stored data line): store
- Processor to Events API (dashed): ack
9. Fitting text
Text never overflows its box, and padding stays the same on every box.
| Situation | What happens |
|---|---|
| A label is longer than a card | It wraps onto more lines, up to three, and the card grows taller. |
| One word is longer than a card | The card widens to hold it, up to 400px. |
| Text is still too long | It is cut with an ellipsis and the diagram gets warning W203. The full text stays in the text description. |
| A group name is long | The corner tab grows to fit it on one line, up to the width of the widest card (400px). Past that the name is cut with an ellipsis and warning W203. |
| A message label is long in a sequence diagram | The columns spread apart to make room, and W202 appears if the diagram then becomes too wide. |
The fix for a warning is almost always shorter text. Move detail into sub=, a note, or the prose around the diagram.
10. Quality warnings
Warnings never stop rendering. They point at things a reader will notice.
| Code | Meaning | Fix |
|---|---|---|
W200 | Too many nodes, tables, or actors | Split along a group boundary or by domain. |
W201 | A node is not connected to anything | Connect it or remove it. |
W202 | The diagram is wider than a Markdown column and its text would shrink | Use direction=down, shorten labels, or split it. |
W203 | Text was cut with an ellipsis, in a node, an actor, or a group name | Shorten the text. |
W204 | A node has no icon=, and its label names one product | Add the icon= the warning gives, or write icon=none. |
W205 | A group has no type=, and its name matches one group type | Add the type= the warning gives. |
See also
| Doc | Why |
|---|---|
| Cloud architecture | Typed groups, provider icons, and how to map a product name to an icon |
| Architecture patterns | Ready-made, checked diagrams to start from |
| Language reference | Every type, tone, icon, arrow, and diagnostic code |
| Writing diagrams with an agent | The validate and fix loop |
| Diagrams in Markdown and on GitHub | Putting diagrams in a document |
| How a diagram is drawn | What happens between your text and the picture |