- examples/: c4-standard-web-app, c4-e-shop (was online-shop), c4-event-based-integration; the same models the extension offers under Archify | New Model… - model-scaffold --example <name> copies one and renames id, name, description and the overview title; never overwrites - tests cover every example and the copy; docs updated
11 KiB
Authoring Archify diagrams
What an agent needs to write a diagram that validates and reads well. The schemas under scripts/vendor/archify/schemas/ are the authority; read the kind's schema and common.schema.json before using a field not listed here. Archify rejects unknown keys everywhere (additionalProperties: false).
Every diagram
schema_version: 1 # 1 for architecture, sequence, dataflow; 2 for new workflow and lifecycle
diagram_type: architecture # architecture | workflow | sequence | dataflow | lifecycle
meta:
title: Containers # required
output: containers.html # required: relative POSIX path ending in .html, no "..", no drive letters
Optional meta keys: subtitle (omit unless asked), viewBox: [w, h] (omit for architecture; Archify measures), legend (mode: auto|all|hidden, entries: { backend: { label: Service } } to rename), locale (en, zh-CN; others need translations), visual_preset (classic default, signal-flow, blueprint, editorial; only on request), quality_profile (standard, showcase), animation: trace, repository plus node sources for source-backed diagrams, cards (summary blocks under the diagram: dot, title, items).
Ids match ^[a-zA-Z][a-zA-Z0-9_-]*$ and are unique in their collection. Relationship ids are optional (they give stable #relation= links).
Node fields (all kinds)
| Field | Values |
|---|---|
type |
frontend, backend, database, cloud, security, messagebus, external (lifecycle states: start, active, waiting, decision, success, failure, neutral, external) |
label |
required; keep it short, the box is small |
sublabel |
one fact: technology, protocol, role |
tag |
small badge text (not in sequence participants) |
icon |
corner symbol: calendar, clock, person, briefcase, flag, moon, a type name, or none |
brand |
an Archify brand id (see Brands) |
sources |
1 to 3 { path, line, end_line, label } repository references, with meta.repository |
type sets colour and legend entry. Pick by role: people and outside systems external; the own system as one box cloud; UIs frontend; services and jobs backend; stores and caches database; queues and topics messagebus; auth, gateways, secrets security.
Architecture
Collections: components (required), connections, boundaries, cards, layout.
Placement is yours; Archify routes the lines but never moves boxes. Use free positions on this grid, which is what the templates and examples/c4-e-shop use:
- box
size: [130, 60]([150, 60]for a system-level box with a longer label) - columns at x = 40, 250, 460, 670, 880 (step 210: an 80px clear gap between boxes)
- rows at y = 60, 160, 260, 360, 460 (step 100: a 40px gap)
Rules of thumb that keep the validator quiet:
- Place by connections first: main path left to right, neighbours adjacent; a store or branch directly above or below its owner, centred, so the edge is one straight segment; a return edge on the side without branches.
- A horizontal label on the 80px gap fits about 9 ASCII characters (
6.5px per character + 21px). Longer labels: widen the step (250), or setlabelAtwhen the validator asks. - Label width inside a box:
6.5px per character + 13pxmust be under the box width; a 130px box takes about 18 characters. Sublabel:5.4px per character + 8px. - A side with k connections needs
32 + 14 * (k - 1)px; spread a hub's partners over two or three sides. - External actors stay outside the boundary, including its padding.
- Omit
meta.viewBox; Archify sizes the canvas.
Connections: from, to, label, variant (default, emphasis for the main path, security, dashed), fromSide/toSide (left|right|top|bottom, pin only when needed), route (auto, straight, orthogonal-h, orthogonal-v), via: [[x, y], ...] (explicit waypoints, must leave the source outward on the pinned side), labelAt: [x, y], labelDx, labelDy, labelSegment, width.
Boundaries: kind: region | security-group, label, wraps: [ids], pad. A boundary states a real ownership, trust or deployment fact; it does not replace connections.
Alternative to pos: row/col per component with layout: { mode: grid, origin, cols, cellW, cellH, gapX, gapY }. Do not mix pos and row/col in one diagram: Archify stops with internal/unclassified instead of a readable error.
Workflow (schema_version 2)
schema_version: 2
diagram_type: workflow
meta: { title: Change approval, output: workflow.html }
lanes:
- { id: author, label: Author }
- { id: review, label: Reviewers }
mainPath: [draft, review, approved, released]
nodes:
- { id: draft, lane: author, col: 0, type: frontend, label: Draft change }
- { id: review, lane: review, col: 1, type: security, label: Review, sublabel: two approvals }
edges:
- { id: submit, from: draft, to: review, label: submit }
- { from: review, to: draft, label: changes requested, role: branch, variant: dashed }
col is 0..5, the logical step. mainPath lists the happy path in order; keep it monotonic. edges.role: main, branch, async, return, error. Optional phases and groups (fromCol, toCol) and yOffset per node (symmetric values like -90/0/90 inside one lane). meta.legend.entries renames types for non-software plans (see everyday-plan in the extension's templates: backend as "Time off", icons calendar, briefcase).
Sequence
schema_version: 1
diagram_type: sequence
meta: { title: Login, output: sequence.html, viewBox: [960, 520] }
participants:
- { id: client, type: external, label: Client }
- { id: api, type: backend, label: API }
messages:
- { from: client, to: api, y: 160, label: POST /login, variant: emphasis }
- { from: api, to: client, y: 210, label: 200 OK, variant: return }
Participants are columns in order; messages run top to bottom by y (50px apart is comfortable). variant adds return. Optional note per message, segments (from, to, label) and activations (participant, from, to). meta.column_fit: spread uses a wide viewBox for column distance.
Dataflow
schema_version: 1
diagram_type: dataflow
meta: { title: Sales reporting, output: dataflow.html, viewBox: [960, 420] }
stages: [{ label: Sources }, { label: Processing }, { label: Storage }]
nodes:
- { id: shop, type: frontend, label: Web shop, stage: 0, row: 0 }
- { id: etl, type: backend, label: Nightly ETL, stage: 1, row: 0 }
flows:
- { from: shop, to: etl, label: orders }
- { from: crm, to: etl, label: customers, classification: PII, variant: security }
Stages are columns (index into stages), rows separate parallel streams. Label only data contracts, classifications and cross-boundary movement.
Lifecycle (schema_version 2)
schema_version: 2
diagram_type: lifecycle
meta: { title: Order status, output: lifecycle.html }
lanes:
- { id: main, label: Normal path }
- { id: exits, label: Exits }
states:
- { id: placed, type: start, label: Placed, lane: main, col: 0 }
- { id: paid, type: active, label: Paid, lane: main, col: 1 }
- { id: cancelled, type: failure, label: Cancelled, lane: exits, col: 0 }
transitions:
- { id: pay, from: placed, to: paid }
- { id: cancel, from: placed, to: cancelled, variant: security }
Each lane is a row (main first, terminal last); col 0..4 is one shared x grid, so put an exit in the column of the state it leaves. Every transition is authored, including the main path. A recoverable failure needs a real transition back. Omit viewBox in v2.
Brands
brand puts a product logo on a node. Use the canonical id from the bundled catalogue:
node scripts/vendor/archify/bin/archify.mjs brands "kafka" --json # id apache-kafka
node scripts/vendor/archify/bin/archify.mjs brands --json # all 107
Ids include postgresql, redis, mysql, mongodb, apache-kafka, rabbitmq, docker, kubernetes, terraform, node-js, react, vue, python, go, rust, typescript, spring, django, fastapi, github, gitlab, jenkins, grafana, prometheus, stripe, paypal, openai, anthropic, claude, google-cloud, cloudflare, vercel, supabase, snowflake, databricks. An unknown brand is a validation error; omit brand rather than guess. A URL brand needs archify brands capture <url> first (network) and the returned digest.
Validator output and fixes
task model:validate and task diagram:validate map Archify's diagnostics to the file, line and JSON pointer. Fix in this order, one coherent change at a time, validating after each:
| Code or message | Cause | Fix |
|---|---|---|
YAML: ... |
syntax error, usually : inside a plain scalar |
> block scalar or quotes |
schema/additionalProperties |
an unknown key | remove or rename it; model keys only in registries and ref nodes |
schema/... other |
wrong enum, missing meta.output, bad id, schema_version not allowed for the kind |
read the message; the pointer names the node |
Label "..." is wider than component |
label too long for size |
widen size[0] or shorten |
Components "a" and "b" are less than 8px apart |
overlap | use the suggested pos |
clean-flow/edge-through-node |
an edge crosses an unrelated box | move the box off the corridor, or fromSide/toSide, or via |
clean-flow/endpoint-side-direction |
via leaves the box inward |
first waypoint must be outward on the pinned side |
composition/label-gap, label_route_clearance |
labels collide with routes, boxes or each other | enlarge the gap or set the suggested labelAt; never delete the label |
layout/boundary-out-of-bounds, layout/route-out-of-bounds |
negative coordinates or an authored viewBox too small |
move inward, or remove/enlarge viewBox |
composition/excessive-route-detour |
an explicit via is far longer than needed |
remove the via |
Component ids must be unique |
duplicate id | rename, or id: next to ref: |
brand/unknown |
brand id not in the catalogue; the message lists the closest ids | use one of them, or omit brand |
--quality=showcase turns composition warnings (crossings, corridors, border runs, route rhythm) into failures; use it for diagrams that will be presented. --layout-json on the plain Archify CLI (archify validate architecture x.json --layout-json) prints the resolved boxes, routes and label positions when a repair needs measurements; get x.json from task model:resolve.
Composition
- One obvious main path,
variant: emphasison it. Branches beside their owner. Stores below their writer. - Labels carry meaning (protocol, action, direction, async). Omit one only when both endpoints imply it.
- Group only real boundaries. A boundary around one box needs a real isolation fact.
- A C4 level answers one question; put details in the next level and link it with
diagram:. - Title only; no subtitle, no preset, no legend overrides unless asked.