Open source · MIT · Astro and Starlight
Architecture diagrams you canpress playon.
Describe your system in YAML. docspp lays it out, documents every box, and replays a request travelling through it, one step at a time. Readers see how it works instead of guessing.
Everything in one picture. Letters on the edges match the interfaces table underneath.
The shopper pays. The order is stored first, charged second, and announced to the rest of the system last.
The gateway has already checked the token, so the order service trusts the user id header.
Planned. Confirmation emails are not sent yet.
| Key | From → To | Kind | Auth | What travels |
|---|---|---|---|---|
| A | Shopper browser → CDN | http | — | HTTPS |
| B | CDN → API gateway | http | — | — |
| C | API gateway → Keycloak | data | client credentials | signing keys |
| D | API gateway → catalog-service | http | — | /products |
| E | API gateway → cart-service | http | — | /cart |
| F | API gateway → order-service | http | — | /orders |
| G | catalog-service → Redis | data | — | cache |
| H | catalog-service → PostgreSQL | data | — | — |
| I | cart-service → Redis | data | — | — |
| J | order-service → PostgreSQL | data | — | — |
| K | order-service → Stripe | channel | API key | payment intent |
| L | order-service → RabbitMQ | queue | — | order.created |
| M | RabbitMQ → notification-worker | queue | — | — |
How it works
Write the model. The picture, the docs and the replay follow.
One YAML file per diagram. Add a concept at a time and watch what it buys you. Each tab below is a real file, compiled and drawn by docspp as the page loaded.
Name the parts and say who talks to whom. Pick a kind and an icon for each node and docspp draws the cards. Edges become arrows, and the legend is generated. There are no coordinates in the file.
title: Photo uploads
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API }
store: { kind: storage, icon: aws-s3, title: Storage }
queue: { kind: queue, icon: rabbitmq, title: Events }
worker: { kind: worker, icon: python, title: Thumbnailer }
db: { kind: database, icon: postgresql, title: Metadata }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }Group related parts into a zone. A group is one line and a node joins it with in:. Each zone is laid out on its own, so the diagram stays tidy as it grows.
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }Tell the story as steps. A packet flies along your edges, hop by hop, and every step is listed under the picture. Pause, scrub, or click a step to jump to it.
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }
scenarios:
upload:
title: Upload a photo
steps:
- browser -> api: POST /photos
- api -> store: { label: PUT original, kind: lookup }
- api -> queue: { label: photo.uploaded, kind: event }
- api -> browser: 202 Accepted
- queue -> worker: photo.uploaded
- worker -> store: PUT thumbnails
- worker -> db: { label: mark ready, kind: lookup }Show a slice of the same model. The processing view keeps four of the six boxes and plays its own scenario. Nothing was redrawn.
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { title: Everything, direction: DOWN }
processing:
title: Processing only
include: [queue, worker, store, db]
scenarios:
upload:
title: Upload a photo
steps:
- browser -> api: POST /photos
- api -> store: { label: PUT original, kind: lookup }
- api -> queue: { label: photo.uploaded, kind: event }
- api -> browser: 202 Accepted
- queue -> worker: photo.uploaded
- worker -> store: PUT thumbnails
- worker -> db: { label: mark ready, kind: lookup }
thumbnails:
title: Make thumbnails
view: processing
steps:
- queue -> worker: photo.uploaded
- worker -> store: { label: GET original, kind: lookup }
- worker -> store: PUT 3 sizes
- worker -> db: { label: mark ready, kind: lookup }Scenarios
One list of steps. Three ways to read it.
A scenario is a short list of steps: who sends what to whom. docspp turns it into packets over the architecture, a sequence diagram or a swimlane story, and keeps all three in step with the player.
The shopper pays. We write the payment down first, charge the card second, and tell the rest of the system last, so a crash never loses money.
The request carries an idempotency key, so a retry cannot charge twice.
This is the only step we do not control, and the slowest one.
Receipts are planned. Today the shopper only sees the on-screen confirmation.
The shopper pays. We write the payment down first, charge the card second, and tell the rest of the system last, so a crash never loses money.
The request carries an idempotency key, so a retry cannot charge twice.
This is the only step we do not control, and the slowest one.
Receipts are planned. Today the shopper only sees the on-screen confirmation.
The shopper pays. We write the payment down first, charge the card second, and tell the rest of the system last, so a crash never loses money.
The request carries an idempotency key, so a retry cannot charge twice.
This is the only step we do not control, and the slowest one.
Receipts are planned. Today the shopper only sees the on-screen confirmation.
Make it yours
Restyle every diagram with a handful of CSS variables.
Every colour, font and shadow in a diagram is a --docspp-* token. Pick a palette and the diagram below changes. The snippet beside it is all the CSS that did it.
The shopper pays. We write the payment down first, charge the card second, and tell the rest of the system last, so a crash never loses money.
The request carries an idempotency key, so a retry cannot charge twice.
This is the only step we do not control, and the slowest one.
Receipts are planned. Today the shopper only sees the on-screen confirmation.
/* Nothing to write: follows the site theme. */
.docspp {
--docspp-accent: #0a6f8f;
}.docspp {
--docspp-bg: #0a2540;
--docspp-surface: #0f3256;
--docspp-zone: #0c2c4c;
--docspp-ink: #e6f2ff;
--docspp-muted: #9bbfe0;
--docspp-line: #35689b;
--docspp-accent: #7fd3ff;
}.docspp {
--docspp-bg: #050806;
--docspp-surface: #0b110c;
--docspp-zone: #08100a;
--docspp-ink: #c9f7d3;
--docspp-line: #1f4a2b;
--docspp-accent: #4ade80;
--docspp-sans: var(--docspp-mono);
}.docspp {
--docspp-bg: #fbfaf8;
--docspp-surface: #ffffff;
--docspp-zone: #f1f0ed;
--docspp-ink: #121212;
--docspp-line: #1f1f1f;
--docspp-accent: #d4281c;
--docspp-shadow: 3px 3px 0 #1f1f1f;
}Dark mode takes its own set under :root[data-theme='dark'] .docspp. Every token is listed in the theming guide.
Icons
Your stack is already in the box.
icon: postgresql finds the logo. Brand logos, monochrome marks and generic UI icons are bundled at build time, so nothing is fetched in the browser. How icons are found.
postgresqlrediskafkarabbitmqkubernetesdockernginxterraformgrafanaprometheuselasticsearchmongodbnodejsgopythonrusttypescriptreactastrographqlcloudflarestripekeycloakgithub-actions
For people and agents
Plain YAML that an agent can write and a machine can check.
The format is small, the schema is published and the validator is strict. Ask Claude Code, Cursor or Copilot to draw your architecture, and the loop below keeps the result honest.
- A published JSON Schema
docspp schema writes it next to your diagrams. Editors autocomplete every key, and agents stop guessing them.
- Errors that say where
File, line and column, with a hint that spells the right key. Add --json and a script can read them.
- A broken diagram never ships
The build fails on diagram errors, so a typo is caught on the pull request and not on the live site.
- References that notice drift
List the source files a box is based on. docspp check warns when one of them moves.
npx skills add AhmeddBasemm/docspp --skill docsppWhat is in the box
Everything a diagram needs to be read, trusted and kept up to date.
Automatic layout
Each zone is laid out on its own and every edge is routed around the boxes. You steer with a few hints, never with coordinates.
Scenarios that play
The happy path, a cache miss, a declined card. Step, scrub, change the speed, and share a link to the exact moment.
Story and sequence views
The same steps as swimlanes with phases, or as a sequence diagram. There is no second file to keep in sync.
One model, many views
Write a node once. Show it in an overview, a backend-only slice, or one view per team.
Docs inside the picture
Click a box for its markdown, its connections and its source files. An interfaces table is generated from the edges.
Brand icons
Thousands of logos and UI icons are bundled at build time. Your own SVG files work too.
Themeable
Light and dark follow the site. Every colour, font and shadow is a CSS variable starting with --docspp-.
Checked at build time
docspp check validates every diagram and the build fails on errors, so what is published always renders.
Static and quick to publish
The output is a static Astro site. The starter ships a GitHub Pages workflow, and any static host works.
Get started
One command to a running docs site.
The starter has a small shop diagram with three scenarios, a docs site around it and a GitHub Pages workflow. Edit the YAML, save, and the page reloads.
- MITFree to use and change.
- Node 22+And pnpm.
- Astro + StarlightMarkdown pages, search, dark mode.
- Static outputAny host works.
- Create the project$
npm create docspp@latest my-docs - Run it$
cd my-docs && pnpm install && pnpm dev - Draw your system
Open
diagrams/shop/diagram.yaml, rename a node, add an edge, press save. Or ask an agent to write the diagram from your code. - Publish
Enable GitHub Pages with the GitHub Actions source and push. The workflow is already there.