If you've ever tried to explain your software architecture to someone a new developer joining the team, a stakeholder who needs to understand the big picture, or even your future self six months later you know how hard it is without a clear diagram. The C4 model solves this by giving you a structured way to describe software systems at four levels of detail. But knowing the theory and actually writing diagram code are two different things. That's where real code examples make all the difference.

What Is the C4 Model, and Why Diagram It as Code?

The C4 model, created by Simon Brown, is a hierarchical approach to documenting software architecture using four levels: Context, Container, Component, and Code. Each level zooms in further, moving from a high-level system overview down to individual classes.

Instead of dragging boxes around in a drawing tool, you describe your architecture using plain text a markup language and a tool generates the diagram for you. This approach has real advantages: diagrams live in version control alongside your code, they're easy to update, and they don't break when someone moves a box in a GUI editor and forgets to save.

You can write C4 diagrams using several markup languages, including PlantUML and Mermaid. Each has slightly different syntax, so seeing actual code examples side by side helps you pick what works for your team.

What Does a C4 Level 1 (Context) Diagram Look Like in Code?

A Context diagram shows your system as a single box and its relationships to users and external systems. It answers the question: "What does this system do, and who interacts with it?"

Here's a PlantUML example using the C4 extension:

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4/master/C4_Context.puml

title System Context Diagram for Online Bookstore

Person(customer, "Customer", "A user who browses and purchases books")
System(bookstore, "Online Bookstore", "Allows customers to browse and buy books online")
System_Ext(payment, "Payment Gateway", "Handles credit card and payment processing")
System_Ext(email, "Email Service", "Sends order confirmation and notification emails")

Rel(customer, bookstore, "Uses", "HTTPS")
Rel(bookstore, payment, "Makes payment requests to", "HTTPS/API")
Rel(bookstore, email, "Sends email via", "SMTP/API")
@enduml

This code produces a clean diagram with three boxes and labeled arrows. The Person element represents an actual human user. System is your software. System_Ext represents external dependencies you don't own.

How Do You Draw a C4 Level 2 (Container) Diagram?

When you zoom into the system boundary, you see containers separately deployable units like web apps, APIs, databases, and message queues. This is the level most teams work at day to day.

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4/master/C4_Container.puml

title Container Diagram for Online Bookstore

Person(customer, "Customer", "Browses and purchases books")

System_Boundary(bookstore, "Online Bookstore") {
  Container(web, "Web Application", "JavaScript/React", "Provides the browsing and checkout UI")
  Container(api, "API Server", "Java/Spring Boot", "Handles business logic and data access")
  ContainerDb(db, "Database", "PostgreSQL", "Stores book catalog, orders, and user data")
}

System_Ext(payment, "Payment Gateway", "Processes payments")

Rel(customer, web, "Uses", "HTTPS")
Rel(web, api, "Makes API calls", "JSON/HTTPS")
Rel(api, db, "Reads and writes", "JDBC")
Rel(api, payment, "Makes payment requests", "HTTPS")
@enduml

Notice how the System_Boundary groups internal containers together, making it clear what you own versus what's external. Each container gets a technology label, which helps developers understand the stack at a glance.

For a deeper breakdown of the full notation syntax, see our C4 model diagram syntax reference.

What Does a Level 3 (Component) Diagram Look Like?

A component diagram zooms into a single container to show its internal modules or services. This level is useful when onboarding developers to a specific service or planning a refactor.

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4/master/C4_Component.puml

title Component Diagram for API Server

Container_Boundary(api, "API Server") {
  Component(catalog, "Catalog Controller", "Spring MVC", "Handles book search and listing endpoints")
  Component(order, "Order Service", "Spring Service", "Manages order creation and status")
  Component(auth, "Auth Filter", "Spring Security", "Validates JWT tokens on requests")
  Component(repo, "Data Access Layer", "Spring Data JPA", "Provides database operations")
}

ContainerDb(db, "Database", "PostgreSQL", "Stores application data")
Container(web, "Web Application", "React", "Frontend UI")

Rel(web, auth, "Sends requests to", "HTTPS")
Rel(auth, catalog, "Forwards authenticated requests")
Rel(auth, order, "Forwards authenticated requests")
Rel(catalog, repo, "Uses")
Rel(order, repo, "Uses")
Rel(repo, db, "Reads and writes", "JDBC")
@enduml

This level requires more maintenance since code structure changes frequently. Many teams only create component diagrams for the most complex or critical containers rather than documenting everything.

How Does the Same Diagram Look in Mermaid Syntax?

Mermaid is another popular choice because it's built into GitHub, GitLab, and many documentation tools natively. While Mermaid doesn't have a dedicated C4 extension like PlantUML, you can represent C4 diagrams using its standard flowchart syntax.

graph TB
  customer["🧑 Customer
Browses and purchases books"]

  subgraph bookstore ["Online Bookstore"]
    web["Web Application
JavaScript/React"]
    api["API Server
Java/Spring Boot"]
    db[("Database
PostgreSQL")]
  end

  payment["Payment Gateway
External"]

  customer -->|"Uses (HTTPS)"| web
  web -->|"API calls (JSON/HTTPS)"| api
  api -->|"Reads/writes (JDBC)"| db
  api -->|"Payment requests (HTTPS)"| payment

The tradeoff is that Mermaid's built-in shapes don't map perfectly to C4's conventions (no native "Person" cylinder shape, for example), but the result is readable and works well in Markdown-based documentation. If you want a detailed comparison between the two markup approaches, check out our PlantUML vs. Mermaid notation comparison.

When Should You Use the Code-Based Approach Over a Drawing Tool?

Diagram-as-code works best when:

  • Your architecture changes often. Updating a text file and regenerating is faster than redrawing in a GUI.
  • You want diagrams in version control. Text files diff cleanly in Git; binary image files don't.
  • Multiple people contribute. Anyone can edit a text file without needing a specific diagramming tool license.
  • You need consistent formatting. The tool handles layout, so diagrams look uniform across your docs.

Drawing tools still win when you need precise pixel-level control, complex custom layouts, or when you're presenting to non-technical audiences who want polished visuals rather than auto-generated diagrams.

What Are Common Mistakes When Writing C4 Diagrams as Code?

After working with teams that adopt C4, the same issues come up repeatedly:

  1. Showing too much detail at the wrong level. A Context diagram with 15 external systems defeats the purpose. Keep Level 1 simple usually 1 central system, a few users, and a handful of external dependencies.
  2. Skipping the Container diagram. Many teams jump from Context straight to Component. The Container level is often the most useful for day-to-day communication.
  3. Inconsistent naming. If your code calls it "User Service" but your diagram says "Account Manager," people lose trust in the diagrams. Keep names aligned with your actual codebase.
  4. Not labeling relationships. An arrow between two boxes tells you there's a connection but not what flows through it. Always add relationship descriptions and protocols.
  5. Letting diagrams go stale. A diagram that doesn't match reality is worse than no diagram. Build diagram updates into your development workflow update them during sprint reviews or code reviews.

What Tools Can Render Your C4 Code Into Images?

Once you've written your diagram code, you need something to render it:

  • PlantUML Runs as a Java application or through a web server. Integrates with most IDEs via plugins. The online PlantUML server renders diagrams in your browser.
  • Mermaid Live Editor A browser-based editor for writing and previewing Mermaid diagrams. Great for quick iterations.
  • Structurizr Simon Brown's own tool, purpose-built for C4 modeling with a dedicated DSL. It adds features like workspace management and diagram navigation.
  • CI/CD pipelines You can automate diagram rendering during builds, so your docs site always shows up-to-date architecture diagrams.

For a broader look at the markup language options and their syntax, our Mermaid architecture diagram reference covers the details.

Practical Checklist: Your First C4 Diagram in Code

Use this checklist to create your first C4 diagram today:

  1. Pick your level. Start with Level 1 (Context) if you don't have any diagrams yet. It gives the broadest value fastest.
  2. Choose a markup language. PlantUML with the C4 extension gives you the most accurate notation. Mermaid works if you need something quick and Markdown-friendly.
  3. List your elements first. Write down the actors, systems, and relationships before touching the code. A five-minute sketch on paper prevents rework.
  4. Write the code and render it. Use the examples above as templates. Replace the names and descriptions with your actual system.
  5. Review with your team. Show the diagram to someone who knows the system. If they spot errors, fix the text file that's the whole point of diagrams-as-code.
  6. Store it with your project. Put the diagram source file in your repository under a /docs or /architecture folder.
  7. Set a review cadence. Revisit diagrams quarterly or whenever a major architectural change ships.

Start with one diagram for your most important system. A single accurate Context diagram is worth more than five outdated, overly detailed ones.