Skip to content
Español

Models and C4 views

An architecture model declares people, software systems, containers, datastores and components once, with their relations. C4 views draw the model at one level, each with its own composition: moving a box changes one view, renaming an entity changes all of them.

language "0.2"
model commerce {
person customer "Cliente"
system shop "Commerce" {
container web "Web" { technology "React" }
container api "API" { technology "HTTP" }
datastore db "Pedidos" { technology "PostgreSQL" }
}
customer -> shop.web: "Compra" as visit
shop.web -> shop.api: "HTTPS" as request
shop.api -> shop.db: "SQL" as persist
}
diagram containers: c4 using commerce {
level container
scope shop
include customer, shop.web, shop.api, shop.db
direction LR
layout assisted
theme "editorial.light"
}
Commerce [Software System] Cliente [Person] Web [Container: React] API [Container: HTTP] Pedidos [Container: PostgreSQL] Compra HTTPS SQL

Views compile into the same model as flow diagrams (decision 0020): entities become elements whose ids are their paths, relations come from the model, and placement, style, class, rule, animate, themes, presets, canvases and every layout mode work as in flow diagrams.

Declaration Entity Where
person <id> ["Label"] [{ … }] a person who uses the systems at the model’s root
system <id> ["Label"] [{ … }] a software system at the model’s root
container <id> ["Label"] [{ … }] an application or service of a system in a system
datastore <id> ["Label"] [{ … }] a container that stores data (cylinder) in a system
component <id> ["Label"] [{ … }] a part of a container in a container

Entity blocks take description "…", technology "…", external (persons and systems outside the organisation), url "…" (a link in exports, export.md) and the entities nested in them. Any other placement is model.invalid-nesting.

  • Paths. An entity is named by the ids from the root: shop.api.orders. Siblings have distinct ids.
  • Relations. a -> b: "Label" as id { technology "…"; description "…" }, at the root or in an entity block. Endpoints are looked up from the enclosing entity outwards, then from the root, after the whole model is read, so declaration order never matters: inside system shop { }, api names shop.api. A relation from an entity to itself or to its own ancestor is model.invalid-relation; an unknown endpoint is ref.unknown-entity, with close paths as hints.
  • Ids. Model ids share the file namespace with diagrams and themes; relation ids are unique per model.

diagram <id>: c4 using <model> { … } names a model of the file, a member import (import commerce from "../models/commerce.meltemi") or a namespace one (using models.commerce).

Level scope Draws Without include
context optional, a system persons and systems all of them; with a scope, the scope and its neighbours
container a system the scope’s containers and datastores, persons, other systems the scope’s children and the entities they relate to
component a container the scope’s components, the other containers of its system, persons, other systems the scope’s children and the entities they relate to
  • include a, b, … lists the entities to draw; exclude a, … removes entities from the list or from the default set.
  • The scope of a container or component view is drawn as a dashed boundary around the children it holds.
  • label shop.api "Orders API" gives an entity, or a relation by its id, another text in this view only. In placement, label <relation> at 50% still moves a relation’s label.
Code When
c4.invalid-level level is missing or not context, container or component
c4.scope-required a container or component view without scope
c4.invalid-scope a scope of the wrong kind; datastores have no components
c4.out-of-level an entity the level does not draw, or the scope itself, in include
c4.unsupported-relation a combination that no model relation stands behind
ref.unknown-model using names no model
ref.unknown-entity an unknown path in scope, include or exclude
  • Lifting. Each end of a model relation is drawn at its nearest drawn ancestor: at the context level, customer -> shop.web goes from customer to shop. A relation whose ends meet, or that has an end with nothing drawn above it, is not drawn.
  • Traceability. Every drawn relation keeps the keys of the model relations it stands for (sources in the JSON, “Stands for” in the inspector) and says when an end was lifted (derived). Direct and lifted relations keep their model id, so styles and routes name them.
  • No merging by default. Two model relations lifted to the same pair stay two arrows.
  • Explicit combination. A relation statement in a view combines every model relation whose drawn ends are its pair: customer -> shop: "Usa" as uses draws one arrow standing for all of them. A combination that no model relation stands behind is c4.unsupported-relation: views never draw arrows for convenience. Views take -> and --> only; <->, <--> and the lines -- and --- are prop.invalid-value on the token, naming the two.
  • Shapes. A person is a head above a rounded body; systems, containers and components are boxes with their label, a type line such as [Container: React] and their description; a datastore is a cylinder; external persons and systems are grey and dashed.
  • Colours. The built-in classes c4_person, c4_system, c4_container, c4_component, c4_external and c4_boundary colour the kinds from the theme’s tokens. They apply first, so theme classes and rules, the view’s rule, class and style win.
  • Relations. The label, with the technology on a second line: [JSON/HTTPS].

A view accepts one declaration of its own, note <id> "Text" [on <entity path>]: an annotation that belongs to the view, never to the model, and is not an entity (include, exclude, label and the relations cannot name it). It is drawn as a flow note, takes class and url in its block, and the view’s style, class, rule and animate statements apply to it. Its place is written in the view’s placement like any element’s.

diagram containers: c4 using commerce {
level container
scope shop
include customer, shop.web, shop.api, shop.db
note cache "Cached 5 min" on shop.api
}

The target of on is an entity the view draws: a path the model has but the view does not draw is ref.unknown-element, with a hint on how to include it, and a dotted leader joins the note to it. Excluding or removing the entity in the editor strips the clause and keeps the note.

link <model>.<path> in a sequence or free diagram, also through imports (link commerce.shop.api, link models.commerce.shop.api), ties an object to an entity. An object that writes no label shows the entity’s; hover and the inspector show its kind, technology and description. A link copies nothing else: a structural relation never becomes a message.

  • A view of an imported model compiles in the project against that model, so validate, export, layout and the editor see the same view.
  • Renaming an entity rewrites its declaration, the model’s relations, every view’s scope, include, exclude, label, combinations, placement and styles, and every link. In VS Code, Rename Symbol reaches every file of the project; the canvas and planEdit (renameEntity) rename within a file.