Diagram type

Mermaid C4 Diagram Syntax & Examples

Reference for Mermaid C4 diagram syntax — C4Context / C4Container / C4Component, Person, System, Boundary, and Rel declarations. Copy-paste architecture examples.

Syntax reference, layout guidance, and ready-to-open examples for this diagram type.

At a glance

A syntax reference for Mermaid's C4 diagram grammar — read this when you need the exact way to declare a person, system, container, or relationship at the four C4 levels (context / container / component / code), then preview and beautify the result in the editor.

What is the C4 modelThe four C4 diagram keywordsElement declarations — Person, System, Container, ComponentRelationships — Rel and labelled connectionsBoundaries — group containers and componentsCommon patterns & gotchasLayout — statement order vs automatic
Syntax reference

Mermaid C4 constructs, one per row

ConstructSyntaxNotes
Diagram keywordC4ContextAlso C4Container, C4Component, C4Dynamic and C4Deployment. Must be the first line.
PersonPerson(cust, "Customer", "A retail banking customer")Alias, label, description. Person_Ext marks someone outside your organisation.
SystemSystem(banking, "Internet Banking", "Lets customers view accounts")System_Ext for external, SystemDb / SystemQueue for storage and messaging variants.
Container / componentContainer(spa, "SPA", "React", "Delivers the UI")Careful: in the container and component families the third argument is the technology and the description moves to fourth. Person and System have no technology argument.
Storage variantsContainerDb(db, "Database", "Postgres", "Stores users")Db and Queue suffixes exist for Container and Component too, each with an _Ext form.
RelationshipRel(cust, banking, "Uses", "HTTPS")Both endpoints must be aliases declared somewhere in the diagram — forward references are fine.
Directed / two-wayRel_U Rel_D Rel_L Rel_R BiRelDirection suffixes accept either the letter or the word (Rel_Up), plus Rel_Back and Rel_Neighbor.
Boundary blockSystem_Boundary(b0, "Bank") { … }Also Enterprise_Boundary, Container_Boundary and plain Boundary. The brace opens a block that groups the elements inside it.
Deployment nodeDeployment_Node(dn, "AWS eu-west-1")Node, Node_L and Node_R are accepted aliases.

Syntax reviewed

Rendered proof
C4 container diagram for a ride-hailing platform — nested boundary, data store, event queue, and external dependencies.
Theme · Atlas
Open this diagram in editor
Ride-hailing platform [SYSTEM] Container diagram — ride-hailing platform Books trips withCalls[HTTPS / JSON]Reports location via[gRPC]Routes requests to[HTTP]Requests driver match[gRPC]Publishes trip events[Kafka]Reads and writes[SQL]Charges fares via[HTTPS]Consumed byRoutes and ETAs[HTTPS] Rider Requests and pays for trips Driver Accepts and fulfils trip requests Rider App [React Native] Trip booking, live tracking,payments API Gateway [Kong] Routing, auth, rate limiting Trip Service [Go] Matching, pricing, trip lifecycle Dispatch Service [Elixir] Driver location ingest andassignment Trip Store [PostgreSQL] Trips, fares, receipts Trip Events [Kafka] Trip state changes fanned outto consumers Notification Worker [Node.js] Push and SMS fan-out Maps Provider Routing and ETAs Payment Processor Card charges and driver payouts
View Mermaid sourcePlain-text diagram syntax — copy or edit directly.
diagram.mmd
1C4Container
2 title Container diagramride-hailing platform
3 Person(rider, "Rider", "Requests and pays for trips")
4 Person(driver, "Driver", "Accepts and fulfils trip requests")
5 System_Boundary(platform, "Ride-hailing platform") {
6 Container(riderApp, "Rider App", "React Native", "Trip booking, live tracking, payments")
7 Container(gateway, "API Gateway", "Kong", "Routing, auth, rate limiting")
8 Container(trips, "Trip Service", "Go", "Matching, pricing, trip lifecycle")
9 Container(dispatch, "Dispatch Service", "Elixir", "Driver location ingest and assignment")
10 ContainerDb(tripsDb, "Trip Store", "PostgreSQL", "Trips, fares, receipts")
11 ContainerQueue(events, "Trip Events", "Kafka", "Trip state changes fanned out to consumers")
12 Container(notify, "Notification Worker", "Node.js", "Push and SMS fan-out")
13 }
14 System_Ext(maps, "Maps Provider", "Routing and ETAs")
15 System_Ext(payments, "Payment Processor", "Card charges and driver payouts")
16 Rel(rider, riderApp, "Books trips with")
17 Rel(driver, gateway, "Reports location via", "gRPC")
18 Rel(riderApp, gateway, "Calls", "HTTPS / JSON")
19 Rel(gateway, trips, "Routes requests to", "HTTP")
20 Rel(trips, dispatch, "Requests driver match", "gRPC")
21 BiRel(dispatch, maps, "Routes and ETAs", "HTTPS")
22 Rel(trips, tripsDb, "Reads and writes", "SQL")
23 Rel(trips, events, "Publishes trip events", "Kafka")
24 Rel(events, notify, "Consumed by")
25 Rel(trips, payments, "Charges fares via", "HTTPS")

What is the C4 model

C4 is an architecture documentation model developed by Simon Brown that describes a software system at four levels of zoom: Context (the system and its external users / systems), Container (the deployable / runnable pieces — services, databases, queues), Component (the major code modules inside a container), and Code (the class-level detail, rarely diagrammed). Mermaid supports the first three cleanly with the C4Context, C4Container, and C4Component diagram keywords. The fourth level (Code) is typically expressed with class diagrams instead. Each C4 level is its own diagram; mixing levels in one chart breaks the cognitive model.

The four C4 diagram keywords

Each diagram starts with one of the C4 type keywords. C4Context is the outermost zoom — the system shown as a single box with external users and external systems around it. C4Container zooms in: the system is decomposed into its containers (services, databases, single-page apps). C4Component zooms in further: a single container's internal components are shown. C4Dynamic is a Mermaid-specific variant for showing dynamic behaviour at any C4 level. Pick the level by audience: Context for non-technical readers, Container for engineering planning, Component for deep dives.

  • C4Context — system + external actors (for outside audiences)
  • C4Container — decompose the system into deployable pieces
  • C4Component — decompose a single container
  • C4Dynamic — show runtime behaviour at any level

Element declarations — Person, System, Container, Component

Each level uses different element declarations. Person(id, "Label", "Description") declares a human actor. System(id, "Label", "Description") declares an internal system (your system). System_Ext(id, "Label", "Description") declares an external system you depend on (Stripe, an email service, a third-party API). Container(id, "Label", "Tech", "Description") declares a deployable piece — note the extra Tech parameter (e.g. "Node.js", "PostgreSQL"). Component(id, "Label", "Tech", "Description") works the same way inside a container. Suffix _Ext variants exist for every type to mark external elements with a distinct visual style.

  • Person(id, "Label", "Description") — human / actor (Person_Ext for external)
  • System(id, "Label", "Description") — internal system (System_Ext for external)
  • Container(id, "Label", "Tech", "Description") — deployable piece (Container_Ext for external)
  • Component(id, "Label", "Tech", "Description") — module inside a container

Relationships — Rel and labelled connections

Relationships use Rel(from_id, to_id, "Label") for the default arrow. Rel(from, to, "Label", "Tech") adds a technology / protocol annotation on the connection (e.g. "HTTPS / API", "AMQP", "JDBC"). Directional variants control the visual direction: Rel_U (up), Rel_D (down), Rel_L (left), Rel_R (right) — useful when the auto-layout doesn't match the intended reading direction. BiRel declares a bidirectional relationship without two separate arrows. Labelling relationships well is the highest-leverage part of a C4 diagram — readers learn the integration model from the labels, not from the box layout.

Boundaries — group containers and components

Boundaries group related elements visually. System_Boundary(id, "Label") and Container_Boundary(id, "Label") create a labelled enclosure that other elements are declared inside. Use Enterprise_Boundary(id, "Label") to wrap multiple systems that belong to the same organisation. Boundaries are particularly useful at the Container level to show service boundaries: which containers are deployed together, which cross an organisational boundary, which belong to the same product area. Like with subgraphs in flowcharts, prefer one or two levels of boundary nesting — deeper nesting becomes hard to read.

Common patterns & gotchas

Two patterns dominate real C4 diagrams. Context diagrams almost always have the shape: one System box in the centre, two-to-four Person boxes around it, and a few System_Ext boxes for third-party dependencies. Container diagrams typically show 3-8 Container boxes inside the system, with external Persons and Systems still on the periphery. The most common gotcha: mixing levels in one chart — putting both System and Container declarations in the same diagram breaks C4's discipline and produces an unclear hybrid. Second gotcha: forgetting the "Tech" parameter on Container / Component (the third positional argument) — the diagram still renders but the technology annotations that make C4 diagrams useful are missing.

  • Keep one C4 level per diagram — don't mix System with Container in the same chart
  • Always supply the Tech parameter on Container / Component for the integration annotations
  • Use _Ext variants for external (third-party) elements — visual distinction matters

Layout — statement order vs automatic

Mermaid's native C4 renderer has no layout algorithm: elements are packed into rows in the order you declare them, and the only controls are UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1") (how many shapes / boundaries fit per row) and reordering your statements until the diagram reads correctly. That works for small context diagrams but becomes a fight on anything with boundaries or more than a handful of relationships. Beauty Diagram's editor lays C4 out automatically instead: relationship structure determines placement, boundaries size themselves around their contents (including nested boundaries), and elements declared without any Rel are grouped beside related elements of the same kind rather than scattered. The same source renders both ways, so you can keep UpdateLayoutConfig lines in the file for native renderers — they're ignored here without error. C4-PlantUML documents are accepted directly too, @startuml wrapper and !include lines included.

  • Native Mermaid: layout follows statement order; tune with UpdateLayoutConfig and reordering
  • Beauty Diagram editor: automatic layout from relationships and boundaries — no manual tuning
  • UpdateElementStyle / UpdateRelStyle lines are accepted and safely ignored — themes own the styling
FAQ

Mermaid C4 Diagram Syntax & Examples — frequently asked questions

What's the difference between C4Context, C4Container, and C4Component in Mermaid?

Each is a different zoom level on the same system. C4Context is the outermost view — your system shown as a single box with external users and external systems around it. C4Container zooms inside: the system is broken into its deployable pieces (services, databases, single-page apps). C4Component zooms further into one container, showing its internal modules. Pick the level by audience: Context for non-technical stakeholders, Container for engineering planning, Component for deep architectural reviews. Mermaid also supports C4Dynamic for showing runtime behaviour at any level.

When should I use System_Ext vs System?

Use System(id, "Label", "Description") for systems you own and operate — the systems that are inside your team's scope. Use System_Ext(id, "Label", "Description") for third-party systems you depend on but don't control: Stripe, Auth0, an email provider, an external API. The _Ext suffix exists for Person_Ext (external user / customer outside your organisation) and Container_Ext (external container) too. The visual distinction is important — it tells readers what is in-scope for changes vs what is a fixed external dependency.

How do I label the technology on a C4 container?

The Container and Component declarations take a third positional argument for technology: Container(api, "API Service", "Node.js / Hono", "Public REST API"). This appears as a small annotation under the main label in the rendered diagram. The same pattern applies on relationships: Rel(api, db, "Stores users", "PostgreSQL / JDBC") adds the protocol or technology annotation to the connection. These technology labels are what make C4 diagrams useful for engineering planning — without them the diagram is just boxes.

Can I group containers visually inside a C4 diagram?

Yes — use boundaries. System_Boundary(id, "Label") wraps containers that belong to the same system. Container_Boundary(id, "Label") is for components inside a container. Enterprise_Boundary(id, "Label") is the outermost — wraps multiple systems that belong to the same organisation. Boundaries are declared as parent elements that contain child declarations inside them, like a subgraph in a flowchart. Don't nest boundaries deeper than two levels — the visual hierarchy becomes hard to follow.

Should I mix multiple C4 levels in one diagram?

No. The whole point of C4 is that each level has a different audience and a different level of abstraction — mixing them in one diagram produces an unclear hybrid that doesn't serve either audience well. Make a separate diagram per level: the Context diagram for the executive summary, the Container diagram for the planning doc, the Component diagram for the deep-dive design review. Link the levels in the surrounding documentation rather than stacking them together visually.

How do I control the layout of a Mermaid C4 diagram?

In native Mermaid, layout follows your statement order: elements pack into rows as declared, and UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1") changes how many fit per row. There is no automatic placement, so polishing a native C4 diagram usually means reordering declarations until it reads correctly. Beauty Diagram's editor takes the opposite approach — placement is computed from the relationship structure and boundary nesting, so reordering statements isn't needed and UpdateLayoutConfig is safely ignored. If a relationship arrow reads in the wrong direction conceptually, fix the Rel(from, to, …) argument order rather than the layout.

Why do my unconnected C4 elements end up in odd positions?

Elements declared without any Rel give a layout engine nothing to anchor them to, so most renderers either pack them in declaration order (native Mermaid) or push them to the canvas edge. Beauty Diagram groups unconnected elements beside connected elements of the same kind inside the same boundary — unconnected Persons line up next to the connected Person, spare databases sit with the other data stores. If an element genuinely belongs somewhere specific, the most reliable fix in any renderer is to give it at least one labelled relationship; the connection also tells readers why the element is in the diagram at all.

Does this work with C4-PlantUML sources?

Yes. Paste a C4-PlantUML document as-is — the @startuml wrapper, the !include <C4/C4_Context> line (any include spelling: the stdlib form, a relative path, or the raw GitHub URL), and directives like LAYOUT_LEFT_RIGHT() or SHOW_LEGEND() are all understood. The diagram level is read from the include name, LAYOUT_LEFT_RIGHT() flips the reading direction, and the styling directives are safely ignored in favour of themes. The same elements, boundaries, and relationships render identically whether they arrive as Mermaid C4 or C4-PlantUML.