Mermaid Flowchart Guide: Syntax, Examples & Best Practices
AI coding assistants are no longer a novelty — they're the default. Copilot, Cursor, Claude Code, and dozens of others have fundamentally changed how we write software. But while AI excels at generating code, there's one area where traditional tools have lagged behind: diagrams.
Drag-and-drop diagram editors were designed for a mouse-first world. They often store diagrams in verbose, layout-oriented XML, JSON, or proprietary formats that are harder for people and AI assistants to modify reliably. If you've ever asked an AI to "add an error handling branch to this flowchart," you know the frustration: the model may understand the change, but editing the underlying format without breaking the layout is difficult.
Mermaid changes this. It's a text-to-diagram language where flowcharts, sequence diagrams, and architecture maps are expressed as plain, human-readable code. And because AI models are trained on vast amounts of text — including Mermaid syntax — they can generate, modify, and debug diagrams as naturally as they write Python or TypeScript.
Put simply: Mermaid is the AI-native diagramming language.
In this guide, you'll learn Mermaid flowchart syntax not through a dry reference manual, but by building six real-world scenarios that software teams face every day. By the end, you'll have copy-paste templates for login flows, CI/CD pipelines, microservice architectures, and more — plus the confidence to create your own.
Here's how simple it gets. One line:
flowchart TD
Start --> Process --> End
That's a working flowchart. Three nodes, two arrows, zero configuration. Let's build on this foundation.
Quick Reference: The Three Building Blocks
Every Mermaid flowchart is built from just three primitives. Bookmark this section — you'll reference it often.
Direction — controls the layout flow. Mermaid supports five orientation keywords:
| Keyword | Flow direction | Best for |
|---|---|---|
TB / TD | Top → Bottom (default) | Sequential processes, pipelines |
BT | Bottom → Top | Inverted hierarchies, escalation paths |
LR | Left → Right | Architecture diagrams, system overviews |
RL | Right → Left | RTL language support, alternative views |
Nodes — the boxes, shapes, and containers in your diagram. A node is defined by its ID (for referencing in code) and optionally a label (display text). The shape is determined by the brackets you wrap the label in:
flowchart LR
A[Rectangle: process step]
B(Rounded: start/end)
C{Diamond: decision}
Connections — the arrows and lines between nodes. The basic syntax is NodeA --> NodeB for an arrow, or NodeA --- NodeB for a plain line:
flowchart LR
A --> B
C --- D
That's it. Every complex flowchart you'll see in this guide is composed of just these three building blocks combined in creative ways. As we walk through each scenario, I'll call out which new syntax features we're using, so you can build your vocabulary naturally.
Try it yourself: Switch to the Diagram tab to preview each example, or paste the code into the OnUML Editor to modify it and see the result instantly.
Scenario 1: User Login & Registration Flow
Every application starts with authentication. A good login flow handles three states: successful login, failed credentials, and new user registration. Let's diagram one.
flowchart TD
Start([User visits app]) --> HasAccount{Has account?}
HasAccount -->|No| Register[Show registration form]
HasAccount -->|Yes| Login[Show login form]
Register --> SubmitReg[Submit registration]
SubmitReg --> ValidateReg{Valid info?}
ValidateReg -->|No| ShowError[/"Show error message"/]
ShowError --> SubmitReg
ValidateReg -->|Yes| CreateAccount[[Create account]]
CreateAccount --> SendEmail[/"Send welcome email"/]
SendEmail --> Dashboard([Go to dashboard])
Login --> SubmitLogin[Submit credentials]
SubmitLogin --> ValidateLogin{Valid credentials?}
ValidateLogin -->|No| ShowError
ValidateLogin -->|Yes| Dashboard
What's happening here:
([text])— Rounded rectangle (stadium shape): Use for start and end points. The rounded corners visually signal "entry" and "exit" — in this case,([User visits app])and([Go to dashboard]).[text]— Sharp rectangle: The workhorse node for process steps:[Show registration form],[Submit credentials].{text}— Diamond: The universal symbol for a decision point:{Has account?},{Valid credentials?}.-->|label|— Labeled arrows: Every branch out of a diamond should answer the question it asks.-->|Yes|and-->|No|make the logic self-documenting without extra annotation.[[text]]— Subroutine shape: The double-border rectangle signals a complex sub-operation.[[Create account]]tells the reader this isn't a simple step — it involves database writes, hashing, and possibly more.[/"text"/]— Parallelogram: Represents input or output:[/"Send welcome email"/].
Key takeaway: A flowchart's readability depends more on shape choice than on code style. Diamonds for decisions, rounded rectangles for start/end, and labeled arrows on every branch — these three habits will make every diagram you create instantly understandable.
📋 New syntax in this scenario
([label])— Rounded rectangle (stadium) for start/end[label]— Sharp rectangle for process steps{label}— Diamond for decisions-->|label|— Labeled arrow connections[[label]]— Subroutine shape[/"label"/]— Parallelogram for I/O["label with special characters"]— Quoted label text
Scenario 2: CI/CD Deployment Pipeline
Continuous integration and delivery pipelines are perfect candidates for flowcharts — multiple stages, conditional paths, and a natural top-to-bottom progression. Here's a Docker-based CI/CD pipeline with build, test, deploy, and rollback stages.
flowchart TD
Commit([Developer pushes code]) --> Build[Docker build image]
Build --> UnitTest{Run unit tests}
UnitTest -->|Fail| NotifyDev[/"Notify developer"/]
NotifyDev --> Fix[Fix & recommit]
Fix --> Commit
UnitTest -->|Pass| IntegrationTest{Run integration tests}
IntegrationTest -->|Fail| NotifyDev
IntegrationTest -->|Pass| Push[[Push to container registry]]
Push --> Deploy{Deploy strategy?}
Deploy -->|Canary| Canary[Deploy to 10% traffic]
Deploy -->|Blue-Green| Switch[Switch to new environment]
Canary --> Smoke{Run smoke tests}
Switch --> Smoke
Smoke -->|Pass| FullRollout[Roll out to 100%]
Smoke -->|Fail| Rollback[Rollback deployment]
Rollback --> NotifyOps[/"Notify on-call"/]
NotifyOps --> Fix
FullRollout --> Done([Deployment complete])
What's happening here:
subgraph/end: You'll notice this diagram is long. In a real team, you'd use subgraphs to organize it into logical phases — Build, Test, Deploy. I kept this one flat to show the complete flow first; subgraphs arrive in Scenario 3.[(text)]— Cylinder (database shape): We haven't used it here, but[(Container Registry)]would be the ideal shape for the registry step — the cylinder tells readers "this is a persistent store."[[text]]— Subroutine:[[Push to container registry]]uses the subroutine shape because pushing involves tagging, authentication, and uploading — not a single action.- Multiple decision diamonds in sequence: A pipeline is fundamentally a chain of checks. Each
{test}node is a gate: pass through or loop back. The loop-back pattern (NotifyDev --> Fix --> Commit) uses a backward arrow — Mermaid handles this cleanly, placing the nodes without manual layout. - Branching after a decision:
{Deploy strategy?}splits into two parallel paths (Canary and Blue-Green), which then converge at{Run smoke tests}. Mermaid's auto-layout handles convergence gracefully when both branches point to the same target.
Why Mermaid beats static images here: CI/CD pipelines change weekly — new stages, updated tooling, different workflows. A Mermaid diagram lives in your docs/ folder alongside the pipeline config. When the pipeline changes, the diagram changes in the same PR.
Scenario 3: Microservice System Architecture
Architecture diagrams need to show boundaries, data flow, and responsibility — not step-by-step logic. For this, we use subgraphs to group services and LR direction to let the diagram breathe horizontally.
flowchart LR
subgraph Client["🌐 Client Layer"]
Browser[Web App]
Mobile[Mobile App]
end
subgraph Gateway["🚪 API Gateway"]
LB[Load Balancer]
Auth[Auth Middleware]
Router[Request Router]
end
subgraph Services["⚙️ Services"]
subgraph Core["Core Services"]
UserSvc[User Service]
OrderSvc[Order Service]
ProductSvc[Product Service]
end
subgraph Support["Supporting Services"]
NotifSvc[Notification Service]
AnalyticsSvc[Analytics Service]
end
end
subgraph Data["💾 Data Layer"]
PrimaryDB[(Primary DB)]
CacheDB[(Redis Cache)]
Queue[(Message Queue)]
end
Browser --> LB
Mobile --> LB
LB --> Auth
Auth --> Router
Router --> UserSvc
Router --> OrderSvc
Router --> ProductSvc
UserSvc --> PrimaryDB
OrderSvc --> PrimaryDB
OrderSvc -.-> Queue
Queue -.-> NotifSvc
NotifSvc ==> CacheDB
ProductSvc --> CacheDB
AnalyticsSvc -.-> Queue
classDef client fill:#d4e6f1,stroke:#2980b9,color:#1a5276
classDef gateway fill:#fcf3cf,stroke:#f1c40f,color:#7d6608
classDef service fill:#d5f5e3,stroke:#27ae60,color:#1e8449
classDef data fill:#fadbd8,stroke:#e74c3c,color:#922b21
class Browser,Mobile client
class LB,Auth,Router gateway
class UserSvc,OrderSvc,ProductSvc,NotifSvc,AnalyticsSvc service
class PrimaryDB,CacheDB,Queue data
What's happening here:
subgraph Name["display label"]/end: Each subgraph creates a visual container — a labeled box that groups related nodes. Thesubgraphkeyword uses theNameas an internal ID and the["display label"]as the visible title. In this example, we have four top-level subgraphs: Client Layer, API Gateway, Services, and Data Layer.- Nested subgraphs: The Services subgraph contains two inner subgraphs — Core Services and Supporting Services. Nesting works exactly as you'd expect: just place
subgraph/endpairs inside othersubgraph/endpairs. Mermaid renders nested subgraphs as contained boxes within the parent, visually reinforcing the hierarchy. - LR direction: Architecture diagrams read more naturally left-to-right, following the request path from client to data. I chose
flowchart LRhere (compared to theTDdirection in Scenarios 1 and 2) because horizontal layout gives each layer breathing room. As a rule of thumb: TD for pipelines, LR for architecture. [(text)]— Cylinder:[(Primary DB)],[(Redis Cache)], and[(Message Queue)]all use the cylinder shape — the standard notation for persistent storage across all diagramming tools.- Mixed connection styles: Notice three distinct arrow types in the same diagram:
-->(solid, arrow) — synchronous request/response:Browser --> LB-.->(dashed, arrow) — asynchronous or event-driven:OrderSvc -.-> Queue==>(thick, arrow) — emphasis / critical path:NotifSvc ==> CacheDB- Using different arrow styles in the same diagram communicates architectural patterns without needing text annotations.
Key insight from this scenario: Subgraphs don't just make diagrams prettier — they encode architectural boundaries that plain nodes can't express. A reader scanning this diagram instantly sees four layers, two service tiers, and three data stores, even before reading a single node label.
See more: Browse the Examples Gallery for architecture diagrams across Mermaid, PlantUML, and Draw.io.
Scenario 4: Order Error Handling Flow
Error handling is the difference between a demo and a production system. A well-designed error flow has three characteristics: it's visible (colored), it's explicit (labeled), and it's recoverable (loops back). Mermaid's styling system makes this visual.
flowchart TD
Start([New order received]) --> Validate{Validate order}
Validate -->|Invalid| BadRequest[/"Return 400 Bad Request"/]
BadRequest --> End([Done])
Validate -->|Valid| CheckStock{Check inventory}
CheckStock -->|In stock| ReserveStock[Reserve items]
CheckStock -->|Out of stock| NotifyBackorder[/"Notify backorder team"/]
NotifyBackorder -.-> CancelOrder[/"Cancel order & refund"/]
CancelOrder --> End
ReserveStock --> ProcessPayment{Process payment}
ProcessPayment -->|Success| Confirm[Confirm order]
ProcessPayment -->|Declined| Retry{Retry count < 3?}
Retry -->|Yes| ProcessPayment
Retry -->|No| FlagFraud[[Flag for fraud review]]
FlagFraud --> End
Confirm --> SendConfirm[/"Send confirmation email"/]
SendConfirm --> UpdateDB[(Update order DB)]
UpdateDB --> End
classDef success fill:#d5f5e3,stroke:#27ae60,color:#1e8449
classDef failure fill:#fadbd8,stroke:#e74c3c,color:#922b21
classDef retry fill:#fcf3cf,stroke:#f1c40f,color:#7d6608
classDef neutral fill:#d4e6f1,stroke:#2980b9,color:#1a5276
class Start,Confirm,SendConfirm,UpdateDB success
class BadRequest,CancelOrder,FlagFraud failure
class Retry,NotifyBackorder retry
class Validate,CheckStock,ProcessPayment,ReserveStock neutral
What's happening here:
classDef/class— CSS-like styling:classDef className fill,stroke,colordefines a reusable style, andclass node1,node2 classNameapplies it. Four classes here create a semantic color language: green = success path, red = failure/termination, yellow = retry/warning, blue = neutral processing.- Inline
stylealternative: For one-off styling, usestyle NodeID fill:#hex,stroke:#hex. But for diagrams with more than a few nodes,classDefis cleaner and easier to maintain. Change a color once in theclassDefand all assigned nodes update. -.->(dashed arrow): The backorder notificationNotifyBackorder -.-> CancelOrderuses a dashed line — visually distinct from the solid-line happy path. Dashed arrows naturally read as "less direct" or "exception path" to the human eye.- Loop-back with a counter:
{Retry count < 3?}sends control back to{Process payment}if the answer is Yes. This creates a visual loop — and in a real system, this loop corresponds to actual retry logic. The diagram makes the retry ceiling explicit. - Color communicates at a glance: Before reading a single label, a viewer sees green nodes (where we want to end up), red nodes (where we don't), and yellow nodes (where we need attention). This is the power of semantic coloring — it pre-processes the diagram for the reader.
Pro tip: Light backgrounds (fill) with dark strokes (stroke) and readable text (color) work on both light and dark mode. Avoid white text on light backgrounds — it vanishes when exported to documentation.
Scenario 5: SSO Single Sign-On with Interactive Links
Enterprise authentication involves multiple systems — identity providers, service providers, token exchanges. This scenario introduces click events, multi-connections, and two less common node shapes.
flowchart TD
User([User]) -->|1. Request access| SP[Service Provider]
SP -->|2. Redirect to IdP| IdP[Identity Provider]
IdP -->|3. Challenge| User
User -->|4. Submit credentials| IdP
IdP -->|5. Validate| LDAP[(LDAP Directory)]
LDAP -.->|6. User found| IdP
IdP -->|7. Issue SAML assertion| SP
SP -->|8. Grant access| Dashboard{{"Application Dashboard"}}
SP -->|9. Log access event| AuditLog[/"Audit Log"/]
IdP -->|9. Log auth event| AuditLog
click SP "https://docs.onuml.com" "View SP configuration docs" _blank
click IdP "https://docs.onuml.com" "View IdP integration guide" _blank
click Dashboard "/edit" "Open OnUML Editor to customize this diagram"
classDef external fill:#fcf3cf,stroke:#f1c40f,color:#7d6608
classDef system fill:#d4e6f1,stroke:#2980b9,color:#1a5276
classDef data fill:#e8daef,stroke:#8e44ad,color:#6c3483
class User external
class SP,IdP,Dashboard system
class LDAP,AuditLog data
What's happening here:
{{text}}— Hexagon:{{Application Dashboard}}uses the hexagon shape. It's less common than rectangles and diamonds, which makes it effective for drawing attention to a unique node — here, the final destination the user cares about.[/"text"/]— Parallelogram:[/"Audit Log"/]uses the I/O shape to signal "this is a side output" — not part of the main flow, but generated by it.click— Interactive nodes: The threeclicklines at the bottom bind click behavior to specific nodes:
Format:click SP "https://docs.onuml.com" "View SP configuration docs" _blankclick NodeID "URL" "tooltip" target. The_blankopens in a new tab; omit the tooltip or target if unneeded. These click handlers work in OnUML's editor and exported SVGs.- Numbered sequence labels:
|1. Request access|,|2. Redirect to IdP|, etc. Adding step numbers to arrow labels makes multi-step flows traceable. When a colleague asks "what happens at step 4?", the diagram answers precisely. - Multi-connection from one source: Notice
SPconnects to two targets (IdPandAuditLog), andIdPalso connects to two (LDAPandSP). Mermaid handles fan-out naturally — no special syntax needed. - Two sources to one target: Both
SPandIdPconnect toAuditLog(steps 9). This is a common pattern: multiple services writing to a shared audit log or event bus. Mermaid renders this cleanly with converging arrows.
When to use click events: Architecture diagrams that live in your team wiki or onboarding docs benefit enormously from clickable nodes. Link each service box to its source repository, runbook, or monitoring dashboard. One diagram becomes a navigation hub.
Scenario 6: E-Commerce Order Fulfillment (Comprehensive)
Let's bring everything together. This e-commerce order flow combines subgraphs, multiple node shapes, conditional logic, styling, and mixed arrow types into a single diagram — the kind you'd actually use in a system design doc or architecture review.
flowchart LR
subgraph Storefront["🛒 Storefront"]
Cart[Add to cart] --> Checkout{Checkout}
end
subgraph OrderService["📦 Order Service"]
CreateOrder[Create order] --> ValidateOrder{All items available?}
ValidateOrder -->|No| Backorder[Mark backorder]
ValidateOrder -->|Yes| Reserve[[Reserve inventory]]
end
subgraph PaymentService["💳 Payment Service"]
Charge[Charge payment] --> Result{Payment result}
Result -->|Success| Capture[Capture funds]
Result -->|Declined| RetryPayment{Retry?}
RetryPayment -->|Yes| Charge
RetryPayment -->|No| CancelOrder[/"Cancel order"/]
end
subgraph Fulfillment["🚚 Fulfillment"]
Pick[Pick items] --> Pack[Pack order]
Pack --> Ship[Ship to customer]
Ship --> Notify[/"Send tracking email"/]
end
Checkout -->|Create order| CreateOrder
Reserve --> Charge
Capture --> Pick
Backorder --> NotifyBO[/"Backorder notification"/]
CancelOrder --> NotifyCancel[/"Cancellation email"/]
UserDB[(User DB)] -.-> Checkout
InventoryDB[(Inventory DB)] -.-> Reserve
PaymentDB[(Payment DB)] -.-> Charge
classDef store fill:#d4e6f1,stroke:#2980b9,color:#1a5276
classDef payment fill:#fcf3cf,stroke:#f1c40f,color:#7d6608
classDef fulfillment fill:#d5f5e3,stroke:#27ae60,color:#1e8449
classDef error fill:#fadbd8,stroke:#e74c3c,color:#922b21
classDef datastore fill:#e8daef,stroke:#8e44ad,color:#6c3483
class Cart,Checkout,CreateOrder,ValidateOrder,Reserve,Backorder store
class Charge,Result,Capture,RetryPayment payment
class Pick,Pack,Ship,Notify fulfillment
class CancelOrder,NotifyBO,NotifyCancel error
class UserDB,InventoryDB,PaymentDB datastore
What makes this a real production diagram:
- LR direction with subgraphs: The horizontal layout follows the natural request path — storefront → orders → payment → fulfillment — while subgraphs keep each bounded context visually distinct.
- Semantic subgraph naming: Each subgraph uses an emoji prefix (
🛒,📦,💳,🚚) paired with a domain name. The emoji makes the subgraph label scannable in the diagram; the domain name is what you'd reference in conversation ("the Order Service subgraph"). - Cross-subgraph connections:
Checkout -->|Create order| CreateOrderspans from the Storefront subgraph to the Order Service subgraph. Mermaid handles cross-subgraph arrows cleanly — they pass through subgraph boundaries without extra syntax. - Data stores as side connections:
[(User DB)],[(Inventory DB)], and[(Payment DB)]connect to their respective services via dashed arrows (-.->), visually separating data access from control flow. Data stores are deliberately placed outside subgraphs to signal "shared resource" — multiple services could connect to them. - Error paths end the flow:
CancelOrderandBackorderdon't loop back — they terminate. Not every error path needs retry logic; sometimes the correct behavior is to stop and notify. - Five CSS classes, no clutter: Each class maps to a domain (
store,payment,fulfillment,error,datastore). The colors aren't decorative — they're structural. A developer can identify which team owns which nodes by color alone.
📋 Syntax recap: everything used in this guide
| Category | Syntax | Purpose |
|---|---|---|
| Direction | flowchart TD, flowchart LR | Layout orientation |
| Process node | [label] | Standard step |
| Start/End | ([label]) | Entry and exit points |
| Decision | {label} | Conditional branch |
| Subroutine | [[label]] | Complex sub-operation |
| Cylinder | [(label)] | Database / persistent store |
| Hexagon | {{label}} | Distinctive / attention node |
| Parallelogram | [/label/], [\label\] | Input / output |
| Circle | ((label)) | Connector / small node |
| Solid arrow | --> | Synchronous flow |
| Solid line | --- | Association, no direction |
| Dashed arrow | -.-> | Async, event, exception |
| Thick arrow | ==> | Critical / emphasized path |
| Labeled arrow | -->|label| | Self-documenting connection |
| Subgraph | subgraph ID["Title"] / end | Visual grouping |
| Class definition | classDef name fill,stroke,color | Reusable style |
| Class assignment | class node1,node2 name | Apply style to nodes |
| Click event | click NodeID "url" "tooltip" | Interactive node |
| Quoted label | ["text with special characters"] | Safely include spaces or special characters |
Best Practices
The syntax is the easy part. These five principles separate a diagram that helps your team from one they ignore.
1. One story per diagram
A flowchart that covers user login, payment processing, notification dispatch, and database replication is trying to be four diagrams. Split it.
Good: A single diagram showing "Order lifecycle from checkout to fulfillment."
Bad: A single diagram showing "The entire e-commerce platform."
If you find yourself nesting subgraphs three levels deep or using more than ~20 nodes, it's probably two diagrams.
2. Choose direction with intent
- TB (top-to-bottom): Default choice. Natural for sequential processes, pipelines, and anything with a clear "start → end" progression. Humans read top-to-bottom.
- LR (left-to-right): Architecture diagrams, system overviews, horizontal value chains. Gives each subgraph space to grow vertically.
- BT / RL: Use sparingly. BT works for escalation flows (support tier 1 → tier 2 → engineering). RL is mainly for RTL language support.
3. Subgraphs encode architecture, not aesthetics
Don't add subgraphs just to make a diagram "look organized." Each subgraph should represent a real boundary: a team, a service, a deployment unit, a trust zone. If you can't name the boundary, you don't need the subgraph.
4. Color with purpose
Use three, maybe four colors max. Assign them semantically:
- Blue — processing, computation, standard flow
- Green — success, completion, happy path
- Red — failure, error, termination
- Yellow / Orange — retry, warning, needs attention
When every node has a different color, none of them mean anything.
5. Use flowchart consistently
Mermaid supports both flowchart and graph as keywords, and they are functionally similar. This guide consistently uses flowchart because it communicates the diagram type more clearly to readers. Using one keyword throughout a codebase also makes examples easier to scan and maintain.
What's Next?
You now have six battle-tested flowchart templates: authentication, CI/CD, architecture, error handling, SSO, and order fulfillment. Each is ready to copy, paste into the OnUML Editor, and customize for your own system.
Continue your Mermaid journey:
- Mermaid Sequence Diagram Guide: From Beginner to Pro — Master sequence diagrams for API documentation and system interactions.
- Mermaid vs PlantUML: Which One Should You Choose? — A detailed comparison of the two leading text-to-diagram languages. (Coming soon)
- 10 Real-World Flowchart Examples for Software Teams — More copy-paste templates for DevOps, onboarding, and system design.
- How to Convert Mermaid Diagrams to PNG/SVG — Export professional diagrams for documentation, presentations, and wikis.
Ready to create your own? Start diagramming for free — no sign-up required. Write a line of Mermaid, watch it render instantly, and let AI help you build the rest.