Skip to content

PlantUML Guide

PlantUML is the standard diagramming tool for the Arda documentation site. Mermaid should not be used for diagrams embedded in Markdown documents.

Agents (LLM assistants, MCP clients, search bots) rarely consume images and parse PlantUML source unevenly. Every diagram must be paired with a 1–3 sentence prose summary that captures the same semantic content the diagram conveys.

The summary appears immediately before the diagram, written as ordinary prose (not a heading, not a callout). Treat it as the diagram’s alt text in long form.

The sequence below shows the upload flow from the browser through the BFF to S3. The browser obtains a presigned URL, uploads directly to S3, then notifies the BFF on success.
```plantuml
@startuml
title Upload flow
actor Browser
participant BFF
participant S3
Browser -> BFF: GET /upload-url
BFF --> Browser: presigned URL
Browser -> S3: PUT (presigned)
S3 --> Browser: 200
Browser -> BFF: POST /upload-complete
@enduml
```

The summary should:

  • State the diagram’s subject (what is being depicted) and the outcome the reader should take away.
  • Use plain US English. Avoid PlantUML jargon (activation, lifeline) unless the diagram is about PlantUML.
  • Sit between 80 and 400 characters. Longer narratives belong in the surrounding section, not in the summary.

This rule applies to every diagram type — sequence, class, component, state, deployment. Diagrams used purely as visual decoration (e.g., a logo) are exempt; they should also carry an alt attribute (when embedded as <img>) or a one-line caption.

Embed a PlantUML diagram using a fenced code block with the plantuml language identifier:

```plantuml
@startuml
...
@enduml
```

Every diagram must include @startuml / @enduml delimiters — PlantUML will not parse without them.

Use only named colors (e.g., LightYellow, LightGray, Salmon). Do not use hex codes (#ffffcc) or RGB values. Named colors are more readable and consistent across diagrams.

All diagrams must be validated before committing. Render to SVG and verify the output visually. Do not commit a diagram that has not been validated.

Every diagram must have a title declaration immediately after @startuml (and after any configuration directives like hide footbox). The title should describe the scenario or structure being depicted:

@startuml
hide footbox
title Option A: Decoupled Upload + Entity Update

Always hide the participant footbox. Add hide footbox immediately after @startuml:

@startuml
hide footbox
title My Sequence Diagram

Declare participants explicitly in left-to-right architectural order (user on the left, deepest backend/infrastructure on the right). Use actor for human participants and participant for system components:

actor User
participant SPA
participant BFF
participant Operations
participant "S3 Bucket" as S3

For participants with multi-word names, use quoted strings with an alias: participant "S3 Bucket" as S3.

Prefer inline activation (++ suffix on the arrow) over explicit activate/deactivate pairs. Inline activation is more concise and keeps the activation scope visually tied to the message:

User -> SPA ++ : Select image file
SPA -> BFF ++ : POST /api/items/upload-url

Use explicit deactivate only when the deactivation point is not immediately after a return message (e.g., after a note block).

Discrete Activation Blocks (Multi-Step Interactions)

Section titled “Discrete Activation Blocks (Multi-Step Interactions)”

When a diagram shows multiple user interactions with a front-end participant (e.g., navigate, select, submit), each interaction must produce a separate activation block on the front-end lifeline. Do not let one continuous activation bar span the entire diagram.

Pattern: Each user action activates the front-end with ++. Backend calls are nested inside with their own ++ / return pairs. The outer return deactivates the front-end back to the user. Phase separators (== ... ==) appear between activation blocks, not inside them.

@startuml
hide footbox
title Discrete Activation Blocks Example
actor User
participant "Front\nEnd" as FE
participant "Back\nEnd" as BE
== Navigate ==
User -> FE ++ : Navigate to /items
FE -> BE ++ : POST /v1/item/query
return 200 OK (items list)
return Display Items grid
== Select Item ==
User -> FE ++ : Click item row
FE -> BE ++ : GET /v1/item/<itemEId>/full
return 200 OK (item details)
return Open Item Details panel
note over FE
Button enabled only when
card status is FULFILLED.
end note
== Perform Action ==
User -> FE ++ : Click "Add to cart"
FE -> BE ++ : POST /v1/kanban/kanban-card/<eId>/event/request
return 200 OK (KanbanCardStateChange)
FE -> FE : Show toast:\n"Item added"
FE -> BE ++ : GET /v1/kanban/kanban-card/count
return 200 OK (count)
return Badge count updated
@enduml

Key rules:

  1. One activation block per user gesture. User -> FE ++ opens the block; return <display text> closes it.
  2. Nest backend calls inside the front-end block. FE -> BE ++ / return pairs are indented within the outer activation.
  3. Self-calls stay inside the block. FE -> FE : ... does not break the activation — it remains part of the enclosing block.
  4. Notes between blocks. Place note over FE (not note right of FE) after the closing return and before the next phase separator.
  5. Never use FE --> User -- to close a block. Always use return so PlantUML manages the deactivation automatically.

Use dashed arrows (-->) for return messages and notifications from deeper layers to higher layers:

Operations --> BFF : 200 OK\n(url, formFields, objectKey)

Do not use ..> — it is not valid in PlantUML sequence diagrams.

When the return immediately follows the activation, prefer return over explicit deactivate:

BFF -> Operations ++ : POST /items/upload-url
return 200 OK\n(url, formFields, objectKey)

Use == ... == separators to divide the diagram into logical phases or steps. Each separator should have a descriptive label:

== Step 1: Request Upload Credentials ==
...
== Step 2: Upload to S3 ==
...
== Step 3: Update Entity ==

Curly braces are not allowed in message labels — PlantUML interprets them as syntax. Use parentheses or angle brackets instead:

Instead ofUse
A -> B : 200 OK\n{ url, key }A -> B : 200 OK\n(url, key)
A -> B : PUT /items/{itemId}A -> B : PUT /items/<itemId>

Curly braces are safe inside note blocks — they are not parsed as PlantUML syntax there.

Use note right of <participant> for implementation details, validation logic, or design notes. Keep notes attached to the participant performing the action:

note right of Operations
Generates UUID for asset-key.
Constructs S3 key from DQ-001 format.
end note

Use note over <participant> for background processes or cross-cutting concerns:

note over Operations
Periodic cleanup job runs
every 24 hours.
end note

Use \n for line breaks in message labels. Keep to 2-3 lines maximum for readability:

SPA -> BFF ++ : POST /api/storage/upload-url\n(module: operations,\n entityType: item)

Show relationships (inheritance, composition, dependency) and key methods/fields. Omit trivial accessors, getters/setters, and boilerplate methods. The diagram should communicate the design, not replicate the source code.

@startuml
title Item Domain Model
class AClass {
+attr: String
+method(p: Int): Double
}
@enduml

Use standard UML notation:

RelationshipNotation
Inheritance`Child —
Implementation`Class ..
CompositionContainer *-- Part
AggregationContainer o-- Part
DependencyClient ..> Service
AssociationA --> B

Use stereotypes to indicate the nature of a class when it is not obvious:

class ItemService <<service>>
interface S3BucketAccess <<interface>>
class ImageKey <<value object>>

Use the C4 model for architectural views. The C4 library provides macros for the four levels of detail: Context, Container, Component, and Code.

@startuml C4_example
!include <C4/C4_Container>
title Arda Platform — Container View
Person(user, "Shop Manager", "Uses Arda to manage inventory")
Container(frontend, "arda-frontend-app", "Next.js", "Web UI for inventory management")
Container(backend, "operations", "Ktor / Kotlin", "Core domain services and REST API")
ContainerDb(db, "PostgreSQL", "Database", "Bitemporal inventory data")
Rel(user, frontend, "Uses", "HTTPS")
Rel(frontend, backend, "API calls", "REST/JSON")
Rel(backend, db, "Reads/Writes", "JDBC")
@enduml

The following shape macros are available:

@startuml C4_shapes
!include <C4/C4_Context>
!include <C4/C4_Component>
!include <C4/C4_Container>
!include <C4/C4_Deployment>
Person(person, "Person")
Person_Ext(personExt, "External Person")
System(system, "Internal System")
System_Ext(systemExt, "External System")
SystemDb(db, "Database")
SystemQueue(queue, "Message Queue")
Container(container, "Container")
ContainerDb(containerDb, "Container DB")
Component(component, "Component")
Deployment_Node(node, "Deployment Node")
@enduml
@startuml C4_boundaries
!include <C4/C4_Context>
!include <C4/C4_Container>
Boundary(aBoundary, "A Boundary")
Enterprise_Boundary(eBoundary, "Enterprise Boundary")
System_Boundary(sBoundary, "System Boundary")
Container_Boundary(containerBoundary, "Container Boundary")
System_Boundary(b1, "Internal System") {
Container(webApp, "Web Application", "Next.js")
Container(api, "API Service", "Ktor")
}
@enduml
LAYOUT_TOP_DOWN() ' Default top-to-bottom flow
LAYOUT_LEFT_RIGHT() ' Left-to-right flow
HIDE_STEREOTYPE() ' Remove stereotype labels from shapes
PitfallFix
..> in sequence diagramsUse --> for dashed return arrows
{braces} in message labelsUse (parentheses) or <angle brackets>
Missing hide footboxAlways add after @startuml
Unvalidated diagramsAlways render and verify before committing
Overly detailed class diagramsShow design-relevant members only
Missing @startuml/@endumlRequired — PlantUML will not parse without them
Mermaid syntax in MarkdownUse PlantUML exclusively per workspace conventions
Continuous activation bar spanning entire diagramUse discrete activation blocks — one User -> FE ++ / return pair per user gesture
Hex color codes (#ffffcc, #333333)Use named colors (LightYellow, DarkSlateGray) — see Color Manual
  • Use C4 Container or Component diagrams for architecture overviews.
  • Use sequence diagrams for API flows and service interactions.
  • Use class diagrams for domain model documentation.
  • Use HIDE_STEREOTYPE() in C4 diagrams for cleaner output.
  • Keep diagram titles concise and include the view level (e.g., “Container View”, “Sequence: Create Item”).
  • Store complex, reused diagrams as separate .puml files in _assets/plantuml/ and include them via !include.