C4 diagrams
A C4 diagram shows the architecture of a software system at one level of zoom: who uses it, what it is made of, what one part is made of, where it runs, or how one scenario plays out. It follows the C4 model: people, software systems, containers, components and databases, with the boundaries round them and the relationships between them. It is for anyone, human or AI agent, who writes an architecture or design document and wants the picture to say, at a glance, what is inside the system and what is outside it.
TL;DR
| Question | Answer |
|---|---|
| What is it for? | The C4 views of a system: system context, container, component, deployment, and one scenario in order (dynamic). |
| How does it start? | c4 "Title" view=container, then person, system, container, component, and db for the elements, boundary id "Name" kind=system { for a frame round some of them, and a -> b : "Uses" tech="HTTPS" for a relationship. |
| How do I show what is inside and what is outside? | Write external=on on a person or a system that lies outside the scope of the picture. What is inside is tinted, and an external element is grey with a dashed outline. |
| What does each box say? | Its name, a caption such as [Container: Spring Boot] that says what it is and what it is built with, and its description. |
| What does each line say? | A label, and the protocol or mechanism as a tag at the end of the label. --> draws an asynchronous or optional call and ..> an event. |
| Can it number the steps? | Yes. In view=dynamic the relationships are numbered in the order written. |
| Can I convert Mermaid? | Yes. C4Context, C4Container, C4Component, C4Dynamic and C4Deployment become a C4 diagram. Styling and layout calls are dropped and reported. |
| How big may it be? | 80 elements, 160 relationships, 40 boundaries and 4 levels of boundary. Past a limit the diagram is refused with error E140, before anything is drawn. The density warning comes much sooner, at 12 elements. |
| How is it drawn? | A card for each element, a dashed frame with a corner tab for each boundary, and a line with a pill for each relationship. Layout is automatic, and the theme chooses every color. |
Ask your agent
Say who uses the system, what it is made of, and how the parts talk to each other, and name the technology of each part and each line. The agent writes the rest.
Use Plotwork to draw the container diagram of an online bookshop as a C4 diagram. A customer uses a web shop built with React. The web shop calls an orders service built with Node.js over HTTPS, and the orders service reads and writes a PostgreSQL database. The orders service charges cards through a payment provider that we do not run, so mark it as external. Put the web shop, the orders service and the database in a boundary called Bookshop, and give every box a one sentence description.Use Plotwork to draw the system context of our internal expenses tool as a C4 diagram. Employees submit expenses, managers approve them, and finance exports them to the accounting system, which is external. The tool sends an email to the employee when a claim is decided, through the company email system, which is external too. Write what each person does in the label of their line.Use Plotwork to draw the sign in flow of a mobile banking app as a dynamic C4 diagram. The customer enters a password in the mobile app, the app calls the API over HTTPS, the API checks the hash in the database over JDBC, the database answers, the API returns a session token to the app, and the API asks the email system to warn the customer about a new device. Number the steps.Example
This is the container view of an internet banking system, the example the C4 model is usually taught with. It shows a person, containers and a database inside a system boundary, two external systems, descriptions and technologies on the boxes, technology tags on the lines, and a dashed line for an asynchronous call.
c4 "Internet banking system" view=container desc="The containers of the internet banking system and the people and systems around it."
person customer "Personal banking customer" desc="A customer of the bank."system mainframe "Mainframe banking system" external=on desc="Stores all the core banking data."system email "Email system" external=on desc="The bank's email system."
boundary bank "Internet banking system" kind=system { container web "Web application" tech="Spring MVC" desc="Delivers the static content and the single-page app." container spa "Single-page app" tech="Angular" desc="Provides the banking features in the browser." container mobile "Mobile app" tech="Xamarin" desc="Provides a limited set of banking features." container api "API application" tech="Spring Boot" desc="Provides the banking features as a JSON API." db store "Database" tech="Oracle" desc="Stores accounts, credentials, and audit logs."}
customer -> web : "Visits bigbank.com" tech="HTTPS"customer -> spa : "Views balances and pays"customer -> mobile : "Views balances and pays"web -> spa : "Delivers"spa -> api : "Makes API calls to" tech="JSON/HTTPS"mobile -> api : "Makes API calls to" tech="JSON/HTTPS"api -> store : "Reads and writes" tech="JDBC"api -> mainframe : "Makes API calls to" tech="XML/HTTPS"api --> email : "Sends email using" tech="SMTP"email --> customer : "Sends emails to"Elements:
- Personal banking customer, person (A customer of the bank.)
- Mainframe banking system, external system (Stores all the core banking data.)
- Email system, external system (The bank's email system.)
- Boundary: Internet banking system, software system
- Web application, container, Spring MVC (Delivers the static content and the single-page app.)
- Single-page app, container, Angular (Provides the banking features in the browser.)
- Mobile app, container, Xamarin (Provides a limited set of banking features.)
- API application, container, Spring Boot (Provides the banking features as a JSON API.)
- Database, database, Oracle (Stores accounts, credentials, and audit logs.)
Relationships:
- Personal banking customer to Web application: Visits bigbank.com, HTTPS
- Personal banking customer to Single-page app: Views balances and pays
- Personal banking customer to Mobile app: Views balances and pays
- Web application to Single-page app: Delivers
- Single-page app to API application: Makes API calls to, JSON/HTTPS
- Mobile app to API application: Makes API calls to, JSON/HTTPS
- API application to Database: Reads and writes, JDBC
- API application to Mainframe banking system: Makes API calls to, XML/HTTPS
- API application to Email system (dashed): Sends email using, SMTP
- Email system to Personal banking customer (dashed): Sends emails to
A system context view
The context view is the zoomed out picture: the system as one box, the people who use it, and the other systems it depends on. Keep it to a handful of boxes. A person or a system outside the scope is marked external=on, so the reader sees at once what the team owns.
c4 "Internet banking system, context" view=context desc="Who uses the internet banking system, and which other systems it relies on."
person customer "Personal banking customer" desc="A customer of the bank, with personal bank accounts."person staff "Customer service staff" external=on desc="Bank employees who help customers on the phone."system bank "Internet banking system" desc="Lets customers view their accounts and make payments."system mainframe "Mainframe banking system" external=on desc="Stores all the core banking data about customers and accounts."system email "Email system" external=on desc="The bank's email system."
customer -> bank : "Views balances and makes payments using"bank -> mainframe : "Gets account data from, and makes payments using"bank --> email : "Sends email using"email --> customer : "Sends emails to"staff -> mainframe : "Uses"customer -> staff : "Asks questions to" tech="Telephone"Elements:
- Personal banking customer, person (A customer of the bank, with personal bank accounts.)
- Customer service staff, external person (Bank employees who help customers on the phone.)
- Internet banking system, software system (Lets customers view their accounts and make payments.)
- Mainframe banking system, external system (Stores all the core banking data about customers and accounts.)
- Email system, external system (The bank's email system.)
Relationships:
- Personal banking customer to Internet banking system: Views balances and makes payments using
- Internet banking system to Mainframe banking system: Gets account data from, and makes payments using
- Internet banking system to Email system (dashed): Sends email using
- Email system to Personal banking customer (dashed): Sends emails to
- Customer service staff to Mainframe banking system: Uses
- Personal banking customer to Customer service staff: Asks questions to, Telephone
A component view
The component view opens one container and shows what it is made of. The container becomes a boundary with kind=container, and the containers and systems around it stay outside, so the reader sees what talks to what at the edge.
c4 "Internet banking system, API application" view=component desc="The components inside the API application, and what each one talks to."
container spa "Single-page app" tech="Angular" desc="Provides the banking features in the browser."db store "Database" tech="Oracle" desc="Stores accounts, credentials, and audit logs."system mainframe "Mainframe banking system" external=on desc="Stores all the core banking data."system email "Email system" external=on desc="The bank's email system."
boundary api "API application" kind=container tech="Spring Boot" { component signin "Sign-in controller" tech="Spring MVC" desc="Lets users sign in to the banking system." component accounts "Accounts controller" tech="Spring MVC" desc="Provides a summary of the customer's accounts." component reset "Password controller" tech="Spring MVC" desc="Lets users reset their passwords." component security "Security component" tech="Spring bean" desc="Provides the sign-in and password rules." component facade "Mainframe facade" tech="Spring bean" desc="A facade onto the mainframe banking system." component mailer "Email component" tech="Spring bean" desc="Sends emails to users."}
spa -> signin : "Makes API calls to" tech="JSON/HTTPS"spa -> accounts : "Makes API calls to" tech="JSON/HTTPS"spa -> reset : "Makes API calls to" tech="JSON/HTTPS"signin -> security : "Uses"reset -> security : "Uses"reset -> mailer : "Uses"accounts -> facade : "Uses"security -> store : "Reads from and writes to" tech="JDBC"facade -> mainframe : "Makes API calls to" tech="XML/HTTPS"mailer --> email : "Sends email using" tech="SMTP"Elements:
- Single-page app, container, Angular (Provides the banking features in the browser.)
- Database, database, Oracle (Stores accounts, credentials, and audit logs.)
- Mainframe banking system, external system (Stores all the core banking data.)
- Email system, external system (The bank's email system.)
- Boundary: API application, container, Spring Boot
- Sign-in controller, component, Spring MVC (Lets users sign in to the banking system.)
- Accounts controller, component, Spring MVC (Provides a summary of the customer's accounts.)
- Password controller, component, Spring MVC (Lets users reset their passwords.)
- Security component, component, Spring bean (Provides the sign-in and password rules.)
- Mainframe facade, component, Spring bean (A facade onto the mainframe banking system.)
- Email component, component, Spring bean (Sends emails to users.)
Relationships:
- Single-page app to Sign-in controller: Makes API calls to, JSON/HTTPS
- Single-page app to Accounts controller: Makes API calls to, JSON/HTTPS
- Single-page app to Password controller: Makes API calls to, JSON/HTTPS
- Sign-in controller to Security component: Uses
- Password controller to Security component: Uses
- Password controller to Email component: Uses
- Accounts controller to Mainframe facade: Uses
- Security component to Database: Reads from and writes to, JDBC
- Mainframe facade to Mainframe banking system: Makes API calls to, XML/HTTPS
- Email component to Email system (dashed): Sends email using, SMTP
A deployment view
The deployment view shows where containers run. A kind=deployment boundary is a deployment node: a solid frame with a server icon, and tech= names what it is, such as an operating system or a runtime. Nodes nest, and the containers sit in the innermost one. Draw only the relationships a reader needs, and draw them between the containers, as in the container view.
c4 "Internet banking system, live deployment" view=deployment desc="Where the containers of the internet banking system run in production."
boundary laptop "Customer's computer" kind=deployment tech="Windows or macOS" { boundary browser "Web browser" kind=deployment tech="Chrome, Firefox, Safari, or Edge" { container spa "Single-page app" tech="Angular" desc="Provides the banking features in the browser." }}
boundary dc "Big Bank data centre" kind=deployment tech="Ubuntu 22.04" { boundary web "Web server" kind=deployment tech="Apache Tomcat" { container webapp "Web application" tech="Spring MVC" desc="Delivers the static content and the single-page app." } boundary app "Application server" kind=deployment tech="Apache Tomcat" { container api "API application" tech="Spring Boot" desc="Provides the banking features as a JSON API." } boundary data "Database server" kind=deployment tech="Oracle 12c" { db store "Database" tech="Oracle schema" desc="Stores accounts, credentials, and audit logs." }}
spa -> webapp : "Loads from" tech="HTTPS"spa -> api : "Makes API calls to" tech="JSON/HTTPS"api -> store : "Reads and writes" tech="JDBC"Elements:
- Boundary: Customer's computer, deployment node, Windows or macOS
- Boundary: Web browser, deployment node, Chrome, Firefox, Safari, or Edge
- Single-page app, container, Angular (Provides the banking features in the browser.)
- Boundary: Web browser, deployment node, Chrome, Firefox, Safari, or Edge
- Boundary: Big Bank data centre, deployment node, Ubuntu 22.04
- Boundary: Web server, deployment node, Apache Tomcat
- Web application, container, Spring MVC (Delivers the static content and the single-page app.)
- Boundary: Application server, deployment node, Apache Tomcat
- API application, container, Spring Boot (Provides the banking features as a JSON API.)
- Boundary: Database server, deployment node, Oracle 12c
- Database, database, Oracle schema (Stores accounts, credentials, and audit logs.)
- Boundary: Web server, deployment node, Apache Tomcat
Relationships:
- Single-page app to Web application: Loads from, HTTPS
- Single-page app to API application: Makes API calls to, JSON/HTTPS
- API application to Database: Reads and writes, JDBC
A dynamic view
A dynamic view tells one scenario. Write the relationships in the order they happen, and each line carries its number. The same elements as the container view are enough: the order is the new information.
c4 "Sign in to internet banking" view=dynamic desc="What happens, step by step, when a customer signs in."
person customer "Personal banking customer" desc="A customer of the bank."container spa "Single-page app" tech="Angular" desc="Provides the banking features in the browser."container api "API application" tech="Spring Boot" desc="Provides the banking features as a JSON API."db store "Database" tech="Oracle" desc="Stores accounts, credentials, and audit logs."system email "Email system" external=on desc="The bank's email system."
customer -> spa : "Enters credentials into"spa -> api : "Submits the credentials to" tech="JSON/HTTPS"api -> store : "Looks up the hashed password in" tech="JDBC"store -> api : "Returns the credentials to"api -> spa : "Returns a session token to" tech="JSON/HTTPS"api --> email : "Warns about a new device using" tech="SMTP"Elements:
- Personal banking customer, person (A customer of the bank.)
- Single-page app, container, Angular (Provides the banking features in the browser.)
- API application, container, Spring Boot (Provides the banking features as a JSON API.)
- Database, database, Oracle (Stores accounts, credentials, and audit logs.)
- Email system, external system (The bank's email system.)
Relationships:
-
- Personal banking customer to Single-page app: Enters credentials into
-
- Single-page app to API application: Submits the credentials to, JSON/HTTPS
-
- API application to Database: Looks up the hashed password in, JDBC
-
- Database to API application: Returns the credentials to
-
- API application to Single-page app: Returns a session token to, JSON/HTTPS
-
- API application to Email system (dashed): Warns about a new device using, SMTP
The language
A line is one statement. A # starts a comment. Ids are words such as web, api-gateway or _x, text is in double quotes, and options are written key=value. The first line is the header.
The header
c4 "Internet banking system" view=container direction=down desc="What the system is made of."| Part | Meaning |
|---|---|
c4 | The kind. |
"Title" | The title. It is the accessible name of the picture and heads the text description. Optional. |
view= | context, container, component, deployment, or dynamic. The default is container. Only dynamic changes the picture, by numbering the relationships. |
direction= | auto (the default), right, down, left, or up. auto draws the diagram left to right when that fits a Markdown column, and otherwise keeps the narrower of left to right and top to bottom. |
desc= | A plain-language description for the text description. Optional. A desc "text" statement does the same. |
Statements
| Statement | Meaning | Example |
|---|---|---|
person, system, container, component or db, then <id> "Name" desc="..." tech="..." external=on icon=<set:name> tone=<tone> | An element, drawn as a card. desc= says what it does and tech= what it is built with. external=on marks it as outside the scope of the picture. icon= and tone= replace the ones the type has, and icon=none removes the icon. | container api "API application" tech="Spring Boot" desc="Serves the data." |
boundary <id> "Name" kind=<kind> tech="..." {, the elements inside, then } | A frame with its name and a caption in a corner tab, round the elements written inside it. Boundaries nest to four levels. The } is on a line of its own. | boundary bank "Internet banking" kind=system { |
<from> -> <to> : "Label" tech="..." | A relationship between two elements. The label and tech= are both optional, and either may come alone. | spa -> api : "Makes API calls to" tech="JSON/HTTPS" |
desc "text" | The description of the diagram. | desc "How a customer signs in." |
tone= takes neutral, blue, violet, aqua, orange, green, amber, or red, and icon= takes a name from the closed list of icons, such as lucide:hard-drive or simple:postgresql. A diagram cannot set a color: the theme chooses every color, in light and dark.
The types of element
Each type has its own caption, tone and icon, so a reader tells them apart before reading a word. The caption is [Type], or [Type: Technology] when tech= is written.
| Type | Caption | Tone | Icon | Use it for |
|---|---|---|---|---|
person | [Person] | violet | lucide:user | A user, a role, or an actor. |
system | [Software System] | blue | lucide:blocks | A whole software system, as one box. |
container | [Container] | blue | lucide:box | An application or a service that runs on its own: a web app, an API, a worker, a mobile app. |
component | [Component] | blue | lucide:puzzle | A part of a container: a controller, a module, a library. |
db | [Database] | aqua | lucide:database | A database, a queue, or any other data store. |
An external person or system says so in its caption, as [External System], and is a plain neutral card with a dashed outline whatever its type.
The kinds of boundary
kind= | Caption in the tab | Frame | Use it for |
|---|---|---|---|
system | [Software System] | Dashed | The system the diagram is about, round its containers. This is the default. |
container | [Container] | Dashed | One container, round its components. |
enterprise | [Enterprise] | Dashed | An organisation, round the systems and people it owns. |
deployment | [Deployment Node] | Solid, with a server icon | A machine, a runtime, or a cluster that containers run on. tech= is its technology. |
A frame inside a frame is shaded one step stronger, so nesting reads at a glance.
Relationship arrows
| Arrow | Line | Meaning |
|---|---|---|
-> | Solid, one head | A call or a flow of data. |
--> | Dashed, one head | An asynchronous or optional call. |
..> | Dotted, one head | An event: a webhook or a message on a topic. |
<-> | Solid, two heads | A two-way call. |
<--> | Dashed, two heads | A two-way asynchronous or optional link. |
A relationship always points, so -- is error E100. It joins two elements, and naming a boundary is error E131. It may name an element written later in the diagram. The label is a sentence that starts with a verb ("Reads from", "Sends email using"), and the technology is the protocol or the mechanism ("HTTPS", "JDBC", "AMQP"): the picture sets the technology in a tag at the end of the pill, so write it as a short name and keep it out of the label.
Make it readable
| Habit | How | Example |
|---|---|---|
| Describe every box | One sentence in desc= says what the element does. A box with only a name makes the reader guess. | desc="Stores accounts and audit logs." |
| Name the technology | tech= goes into the caption of a container, a component or a database. Leave it out of a person and of a system, where it means nothing. | tech="PostgreSQL" |
| Mark what is outside | external=on on every person and system the team does not own. | system mainframe "Mainframe" external=on |
| Keep one level in a diagram | A context view has people and systems, a container view has containers, a component view has components. Split a diagram that mixes them. | |
| Say what a line does | A verb in the label, and the protocol in tech=. | a -> b : "Reads from" tech="JDBC" |
Limits and warnings
Warnings never stop a diagram from being drawn. Errors do.
| Limit | Value | What happens past it |
|---|---|---|
| Elements | 80 | E140 |
| Relationships | 160 | E140 |
| Boundaries | 40 | E140 |
| Nesting of boundaries | 4 | E140 |
| Layers skipped by relationships | 1,000 | E140 |
| Elements before the density warning | 12, plus 2 for each boundary, up to 20 | W200 |
| A relationship's label, and its technology | 400 and 200 px | Cut with an ellipsis, W203 |
| The text of a card | The widest card, 400 px | Cut with an ellipsis, W203 |
Layers skipped counts how far relationships jump over the rest of the diagram: a line that passes over many layers makes the layout work much harder than the number of boxes suggests, so that number is limited too. The limits are checked from the text alone, so a diagram that is too big is refused at once with a hint on how to split it.
| Code | Severity | When | Fix |
|---|---|---|---|
E140 | error | The diagram is over a limit in the table above. | Split the diagram by level. |
E100, E101, E110, E111, E121 to E123, E130, E131, E133 to E136 | error | The shared codes: a syntax error, an unterminated string, an unknown statement or header, an unknown tone, icon, or direction, a duplicate id, an unknown element, an unclosed or unmatched block, an unknown option, or a bad option value. | The message names the line and says what to write. For an unknown element it gives the nearest id. |
W200 | warning | There are more elements than the density limit. | Split the diagram into a context diagram, a container diagram, and a component diagram. |
W201 | warning | An element has no relationship at all, in a diagram that has relationships. | Connect each person to the system they use and each other element to what it talks to, or remove it. |
W202 | warning | The diagram is wider than a column would keep readable. | Use direction=down, shorten the labels, or split it. |
W203 | warning | Text was cut with an ellipsis. | Shorten the text, or move the detail into desc=. |
An id is shared by elements and boundaries, so a boundary and an element with the same id is error E130.
From Mermaid
C4Context, C4Container, C4Component, C4Dynamic and C4Deployment convert into a C4 diagram, and the header word sets the view. Call convert_mermaid on one diagram, or sync_markdown with convertMermaid for a whole file, as the migration guide describes. The conversion writes a C4 diagram, checks it, and returns nothing for one that would not validate.
C4Container title Shop containers Person(customer, "Customer", "Buys books.") Container_Boundary(shop, "Bookshop") { Container(web, "Web shop", "React", "The storefront.") ContainerDb(db, "Orders", "PostgreSQL", "Holds the orders.") } System_Ext(pay, "Payment provider", "Takes cards.") Rel(customer, web, "Browses", "HTTPS") Rel(web, db, "Reads and writes", "SQL") Rel_R(web, pay, "Charges cards using", "HTTPS") UpdateRelStyle(web, pay, $lineColor="red")The converter writes this, and reports one warning M202 for the direction of Rel_R and one M200 for the dropped UpdateRelStyle:
c4 "Shop containers" view=container
person customer "Customer" desc="Buys books."boundary shop "Bookshop" kind=container { container web "Web shop" tech="React" desc="The storefront." db db "Orders" tech="PostgreSQL" desc="Holds the orders."}system pay "Payment provider" desc="Takes cards." external=on
customer -> web : "Browses" tech="HTTPS"web -> db : "Reads and writes" tech="SQL"web -> pay : "Charges cards using" tech="HTTPS"What is kept
| Mermaid | Plotwork | Note |
|---|---|---|
Person, System, Container, Component | person, system, container, component | A person and a system take a description, and a container and a component a technology and then a description. An unquoted label is kept as written. |
SystemDb, ContainerDb, ComponentDb | db | A database of any level. |
Each of those with _Ext | external=on | |
Enterprise_Boundary, System_Boundary, Container_Boundary | boundary with kind=enterprise, system, container | |
Deployment_Node, Node | boundary with kind=deployment and the type as tech= | |
Rel, BiRel | -> and <-> with the label and tech= | |
Rel_Back | The line with its ends swapped | |
RelIndex | -> in a dynamic view | The lines are ordered by their index, and the numbers follow. |
title, a front matter title:, accTitle, accDescr | The title and desc= | A front matter title wins. |
%% comment on a line of its own | Ignored |
What is dropped or changed
| Mermaid | What happens | Code |
|---|---|---|
UpdateElementStyle, UpdateRelStyle, UpdateBoundaryStyle, AddElementTag, AddRelTag, AddBoundaryTag, and the $tags and $sprite arguments | Dropped. The theme owns every color, and the box type and tone= say what an element means. | M200 |
The $link argument | Dropped. A diagram is a static picture with no links inside it. | M201 |
UpdateLayoutConfig, Lay_R and the other Lay_ calls, SHOW_LEGEND, and a direction on Rel_U, Rel_D, Rel_L, Rel_R or Node_L | Dropped. Plotwork lays the diagram out itself. Set direction= on the header to choose the reading direction. | M202 |
| The description of a relationship or of a deployment node | Dropped. A relationship has a label and a technology, and a boundary a name and a technology. | M203 |
A generic Boundary, and a SystemQueue, ContainerQueue or ComponentQueue | Drawn as the nearest thing: a system boundary, and the element with a queue icon. | M204 |
HTML tags, Markdown marks, line breaks and entities such as & in text | Decoded or removed. | M205 |
| An alias that is not a valid Plotwork id | Renamed. The label keeps the original name. | M206 |
| An alias that repeats | The second one is dropped. | M209 |
| An element with no label | Named with its alias. | M216 |
A boundary that is never closed, or a } that closes nothing | Repaired. | M217 |
| A line that cannot be read, or a relationship to an element that is not declared | An error, or with lenient a warning that skips the line. | M110, M218 |
| More than 80 elements, 160 relationships, or 4 levels of boundary | An error. Nothing is converted. | M102 |
See also
| Doc | Why |
|---|---|
| Writing diagrams | The flow, sequence, and schema kinds, and the options every kind shares |
| Examples to copy | Finished diagrams with the prompt that gets an agent to write each one |
| Migrating from Mermaid | The converter, its diagnostic codes, and what carries over |
| Icon reference | Every icon name you can put in icon= |
| State diagrams | The lifecycle of one thing, drawn as states and transitions |
| Class diagrams | Classes, their members, and how they relate |
| Language reference | Every word of the language, one page for each topic |
| Diagram kinds | The kinds, and what each one draws |