C4
Who talks to what, and over which protocol
People, systems, containers, components, databases and queues, inside the boundaries that actually separate them. One diagram per C4 level, and a box can open the diagram one level down, so the whole system reads from the outside in.
Three levels of one system
Relay is drawn at three levels, and each links to the next. Open Relay on the context diagram and you land on its containers. Open Dispatch there and you land on its components.
On the container diagram, the merchant portal and the shipment API link sideways to their endpoint catalogues too. A link can point at any rendered diagram, not only the next C4 level.
What it draws
Every field in the spec, and where it shows up.
ELEMENTS
- kind
- Each kind has its own shape and fill: a figure for a person, a cylinder for a database.personsystemcontainercomponentdatabasequeue
- label · technology
- The name, and a stereotype line under it, for example
[Container: NestJS]. - description
- One sentence on what it does, wrapped inside the box. Boxes in a row grow to fit the longest description, up to five lines. If text still gets cut, the render says which element, and the full text stays in the tooltip and the details panel.
- external
- Drawn grey and marked
external, for things you depend on but do not own: Stripe, an SMS gateway, a partner's API. - link
- A relative path or http(s) URL to another diagram. The box gets a link to open it. Any other URL scheme is rejected.
- sources[]
- File and line. With
meta.repositoryset, the element links to the code.
BOUNDARIES
- kind
- A tinted frame around what it contains.enterprisesystemcontainerdeployment
- contains[]
- Element IDs, or other boundary IDs, which is how boundaries nest. Everything has at most one parent, and a loop in the nesting is an error.
RELATIONSHIPS
- label
- What flows, as a verb phrase: “Reads from”, “Sends emails via”. Long labels wrap to two lines instead of being cut off.
- technology
- The protocol, in brackets under the label:
[HTTPS/JSON],[gRPC],[AMQP],[SQL]. - style
solidfor a synchronous call,dashedfor anything asynchronous.- direction
forward, orbothfor an arrowhead at each end.
LEVEL AND LAYOUT
- meta.level · meta.scope
- Which C4 level this diagram is, and which system or container it zooms into.landscapecontextcontainercomponent
- layout.direction
- Elements are ranked by the direction of their relationships, top to bottom or left to right. The default is left to right when there are more than four ranks. Lines that share a gap each get their own lane, so labels do not pile up in one column.
- row · col
- Pin an element to a cell. No two elements can share one.
- cards[]
- Up to four short notes under the diagram.
Where it reads from
There is no importer for C4, because no single file describes a deployment. The skill reads the places that do and writes the spec:
docker-compose.yml, k8s/, serverless.yml, infra/.
package.json and module files, such as *.module.ts.
HTTP clients, queue clients and SDK imports: stripe, @aws-sdk/client-sqs, nodemailer.
The roles in your auth code.
Check this one before you trust it. Every C4 spec is the agent's reading of your code. validate proves the spec holds together, not that the arrows are right. Read it once.
The spec you keep
{
"schema_version": 1,
"diagram_type": "c4",
"meta": { "title": "Shop", "level": "container" },
"elements": [
{ "id": "buyer", "kind": "person", "label": "Buyer" },
{ "id": "api", "kind": "container", "label": "API",
"technology": "NestJS",
"description": "Takes orders and payments",
"link": "api.endpoints.html" },
{ "id": "db", "kind": "database", "label": "Orders DB",
"technology": "PostgreSQL 15",
"description": "Orders, lines and payments" },
{ "id": "stripe", "kind": "system", "label": "Stripe",
"description": "Charges cards", "external": true }
],
"boundaries": [
{ "id": "shop", "label": "Shop", "contains": ["api", "db"] }
],
"relationships": [
{ "from": "buyer", "to": "api", "label": "Places orders",
"technology": "HTTPS/JSON" },
{ "from": "api", "to": "db", "label": "Reads and writes",
"technology": "SQL" },
{ "from": "api", "to": "stripe", "label": "Charges via",
"technology": "HTTPS" }
]
}The API box links to its endpoint catalogue. Clicking it opens api.endpoints.html, found relative to this diagram.
Aim for about fifteen elements in one diagram. When it gets bigger, draw the next level down and link to it.
The full contract is schemas/c4.schema.json.
What validate stops
Errors
Exit 1. Each one names the field.
dangling-refa relationship or boundary names an ID that does not existid-clasha boundary uses the same ID as an elementself-containa boundary lists itselfmulti-parentone element inside two boundariescycleboundaries nested in a loop
Warnings
Printed, never blocking.
descriptiona box that does not say what it does. People are exemptlabelan arrow that does not say what flowssizemore than 20 elements. Draw another level
The sentences it writes
A docs section can ask for c4.elements, c4.relationships and c4.boundaries. From the Relay build, word for word:
Merchant is a person. Books shipments and pays for them.c4.elements · relay-context.c4#merchant
The Core services deployment boundary contains Shipment API, Scan ingest, Dispatch, Routing engine, Notifier, Relay DB and Parcel events.c4.boundaries · relay.c4
Relationship sentences keep your label whole instead of rewording it into a clause it was not written for.
What it connects to
From a container to its API
Point a container's link at its endpoint catalogue and the reader goes from the box to its routes in one click.
Claims about a box
A claim whose subject is relay.c4#dispatch carries a chip that opens this diagram with Dispatch selected.
All three levels in one file
The dashboard overview lists every drill-down link that points at a diagram inside it, and each one opens that panel.
Dashboard →