# Layered architecture

> Page: https://docs.plotwork.dev/layered-architecture. Index of every page: https://docs.plotwork.dev/llms.txt

A layered diagram stacks the tiers of a system, one wide layer for each, with the blocks that live in each layer and the few links that show how the layers call each other. It is the picture that opens almost every design document: presentation, application, data, infrastructure. It is for anyone, human or AI agent, who writes diagram source.

## TL;DR

| Question | Answer |
|---|---|
| What is it for? | The shape of a system by tier: which blocks sit in which layer, and which layer calls which. Use a flow diagram when the picture is about how a request moves, and layers when it is about where things live. |
| How do I start one? | Write `layers "Title"`, then `layer "Name" {` blocks with `block id "Label"` lines inside, and `}` to close each layer. Write a link between two blocks as `web -> api : "REST"`. |
| What is drawn? | Each layer is a wide frame with a corner tab and a quiet wash of its tone. Each block is a tinted card with an icon, a label, and a note. Links run in the gaps between the rows and never cross a block. |
| Which way does it stack? | The first layer is on top. `direction=up` puts it at the bottom instead, for a stack built on its foundation. |
| What cuts across every layer? | An `aside`: a tall band beside the stack, such as security or monitoring, with its own blocks. It takes no links, because it applies to every layer. |
| How wide is a block? | A layer divides its width into equal columns, and a block is one column wide. `span=2` makes a block as wide as two, so a database can be wider than a service. |
| What are the limits? | 12 layers, 2 asides, 80 blocks, and 80 links. Past one, the source is refused with `E140`. More than 8 blocks in a layer, or more than 20 links, draws a warning. |
| Does Mermaid convert? | Not yet. Mermaid `block-beta` is a grid of blocks with no layers. See [From Mermaid](#from-mermaid). |

## Ask your agent

```text
Use Plotwork to draw a layered diagram of our online banking platform. Four layers: Experience (web app, mobile app, API gateway), Domain services (accounts, payments, ledger), Data (PostgreSQL, Redis, Kafka) and Infrastructure (Kubernetes, the private network). Put security and monitoring in an aside beside the layers, and show the calls between the layers with their protocols.
```

```text
Use Plotwork to draw a three-tier web app: a browser and a CDN, a REST API with an auth service and background jobs, and a database and an object store. Show which blocks call which, and label each call.
```

## Example

**Diagram source**

```diagram
layers "Online banking platform" desc="A customer reaches accounts, payments and cards through one gateway. Each layer calls the layer below it, and security and monitoring cut across all of them."

layer "Experience" tone=blue icon=lucide:monitor {
  block web "Web app" sub="React" icon=simple:react
  block mobile "Mobile app" sub="Swift and Kotlin" icon=lucide:smartphone
  block gateway "API gateway" sub="Routing and limits" icon=lucide:route
}

layer "Domain services" tone=violet icon=lucide:boxes {
  block accounts "Accounts" icon=lucide:wallet
  block payments "Payments" icon=lucide:credit-card
  block ledger "Ledger" icon=lucide:book-open
}

layer "Data" tone=aqua icon=lucide:database {
  block sql "PostgreSQL" sub="Accounts and ledger" icon=simple:postgresql span=2
  block cache "Redis" sub="Sessions" icon=simple:redis
  block events "Kafka" sub="Payment events" icon=simple:apachekafka
}

layer "Infrastructure" tone=neutral icon=lucide:server {
  block k8s "Kubernetes" sub="Three regions" icon=simple:kubernetes
  block net "Private network" sub="Subnets and routing" icon=lucide:cloud
}

aside "Cross-cutting" icon=lucide:shield {
  block iam "Identity" icon=lucide:key-round
  block audit "Audit log" icon=lucide:scroll-text
  block watch "Monitoring" icon=lucide:activity
}

gateway -> accounts : "REST"
gateway -> payments : "REST"
accounts -> sql : "SQL"
ledger <-> sql : "SQL"
payments ..> events : "payment events"
sql --> k8s : "runs on"
```

*Picture: Online banking platform: 4 layers, 14 blocks, 1 aside, 6 links. A customer reaches accounts, payments and cards through one gateway. Each layer calls the layer below it, and security and monitoring cut across all of them.*

**Text description**
- Layer Experience (1 of 4 from the top): Web app (React), Mobile app (Swift and Kotlin), API gateway (Routing and limits).
- Layer Domain services (2 of 4 from the top): Accounts, Payments, Ledger.
- Layer Data (3 of 4 from the top): PostgreSQL (Accounts and ledger), Redis (Sessions), Kafka (Payment events).
- Layer Infrastructure (4 of 4 from the top): Kubernetes (Three regions), Private network (Subnets and routing).
- Aside Cross-cutting, beside the layers: Identity, Audit log, Monitoring.
- Link: API gateway to Accounts: REST.
- Link: API gateway to Payments: REST.
- Link: Accounts to PostgreSQL: SQL.
- Link: Ledger and PostgreSQL, both ways: SQL.
- Link: Payments to Kafka: payment events, asynchronous.
- Link: PostgreSQL to Kubernetes: runs on, optional or indirect.

The four tones carry meaning: blue is what customers touch, violet is the business logic, aqua is where data lives, and neutral is the platform underneath. The PostgreSQL block has `span=2`, so it is as wide as two columns of the Data layer. The dotted line is an asynchronous call, the dashed line is a looser link, and the line with two heads is a two-way call. The aside stands beside the stack and is as tall as it, because security and monitoring touch every layer.

### Three-tier web app

The oldest layered picture. `counts=on` shows how many blocks each layer holds, and the call from the CDN to the object store skips the application layer, so it runs down the lane on the right of the stack instead of through a block.

**Diagram source**

```diagram
layers "Three-tier web app" counts=on desc="A browser talks to a web tier, the web tier calls an application tier, and only the application tier reaches the database."

layer "Presentation" tone=blue icon=lucide:monitor {
  block browser "Browser" sub="Single page app" icon=simple:react
  block cdn "CDN" sub="Static assets" icon=lucide:globe
}

layer "Application" tone=violet icon=lucide:boxes {
  block api "REST API" sub="Node.js" icon=simple:nodedotjs
  block auth "Auth service" icon=lucide:key-round
  block jobs "Background jobs" icon=lucide:workflow
}

layer "Data" tone=aqua icon=lucide:database {
  block db "PostgreSQL" icon=simple:postgresql
  block store "Object store" icon=lucide:hard-drive
}

browser -> api : "HTTPS"
cdn -> store : "origin pull"
api -> db : "SQL"
auth -> db : "SQL"
jobs -> db
```

*Picture: Three-tier web app: 3 layers, 7 blocks, 5 links. A browser talks to a web tier, the web tier calls an application tier, and only the application tier reaches the database.*

**Text description**
- Layer Presentation (1 of 3 from the top): Browser (Single page app), CDN (Static assets).
- Layer Application (2 of 3 from the top): REST API (Node.js), Auth service, Background jobs.
- Layer Data (3 of 3 from the top): PostgreSQL, Object store.
- Link: Browser to REST API: HTTPS.
- Link: CDN to Object store: origin pull.
- Link: REST API to PostgreSQL: SQL.
- Link: Auth service to PostgreSQL: SQL.
- Link: Background jobs to PostgreSQL.

### Hexagonal ports and adapters

The domain depends on nothing, the application owns the ports, and the adapters on both sides plug into them. Three dashed links leave the Ports block, skip the Domain layer, and take a lane each.

**Diagram source**

```diagram
layers "Hexagonal architecture" desc="Driving adapters call the application through ports, and the application reaches the outside world through driven adapters. The domain depends on nothing."

layer "Driving adapters" tone=blue icon=lucide:plug {
  block rest "REST controller" icon=lucide:globe
  block cli "Command line" icon=lucide:terminal
  block consumer "Message consumer" icon=lucide:radio
}

layer "Application" tone=violet icon=lucide:cog {
  block orders "Place order" sub="Use case" icon=lucide:package
  block refunds "Refund order" sub="Use case" icon=lucide:receipt
  block ports "Ports" sub="Interfaces the core owns" icon=lucide:cable
}

layer "Domain" tone=amber icon=lucide:book-open {
  block entities "Entities" icon=lucide:box
  block rules "Pricing rules" icon=lucide:gauge
}

layer "Driven adapters" tone=aqua icon=lucide:plug {
  block repo "Order repository" sub="PostgreSQL" icon=simple:postgresql
  block mail "Email gateway" icon=lucide:mail
  block pay "Payment client" icon=lucide:credit-card
}

rest -> orders : "calls"
cli -> orders
consumer -> refunds
orders -> entities
refunds -> rules
ports --> repo : "implemented by"
ports --> mail
ports --> pay
```

*Picture: Hexagonal architecture: 4 layers, 11 blocks, 8 links. Driving adapters call the application through ports, and the application reaches the outside world through driven adapters. The domain depends on nothing.*

**Text description**
- Layer Driving adapters (1 of 4 from the top): REST controller, Command line, Message consumer.
- Layer Application (2 of 4 from the top): Place order (Use case), Refund order (Use case), Ports (Interfaces the core owns).
- Layer Domain (3 of 4 from the top): Entities, Pricing rules.
- Layer Driven adapters (4 of 4 from the top): Order repository (PostgreSQL), Email gateway, Payment client.
- Link: REST controller to Place order: calls.
- Link: Command line to Place order.
- Link: Message consumer to Refund order.
- Link: Place order to Entities.
- Link: Refund order to Pricing rules.
- Link: Ports to Order repository: implemented by, optional or indirect.
- Link: Ports to Email gateway, optional or indirect.
- Link: Ports to Payment client, optional or indirect.

### A cloud platform on AWS

Provider icons work as they do in a flow diagram: find them with the `search_icons` tool or in the [icon reference](https://docs.plotwork.dev/reference/icons.md). A vendor's own icon is drawn unmodified on its own tile in place of the chip. The link from API Gateway to Lambda joins two blocks of one row, so it runs under the row, inside the layer.

**Diagram source**

```diagram
layers "Serverless platform on AWS" desc="Requests enter at the edge, run in functions, and land in managed data stores. Operations cut across every layer."

layer "Edge" tone=orange icon=lucide:globe {
  block dns "Route 53" icon=aws:route-53
  block cdn "CloudFront" icon=aws:cloudfront
  block waf "WAF" icon=aws:waf
}

layer "Compute" tone=blue icon=lucide:cpu {
  block gw "API Gateway" icon=aws:api-gateway
  block fn "Lambda" icon=aws:lambda
  block flow "Step Functions" icon=aws:step-functions
}

layer "Data" tone=aqua icon=lucide:database {
  block ddb "DynamoDB" icon=aws:dynamodb
  block s3 "S3" icon=aws:s3
  block rds "RDS" icon=aws:rds
}

aside "Operations" icon=lucide:activity {
  block cw "CloudWatch" icon=aws:cloudwatch
  block iam "IAM" icon=aws:iam
}

cdn -> gw : "HTTPS"
gw -> fn
fn -> ddb : "reads and writes"
fn -> s3
flow -> rds
```

*Picture: Serverless platform on AWS: 3 layers, 11 blocks, 1 aside, 5 links. Requests enter at the edge, run in functions, and land in managed data stores. Operations cut across every layer.*

**Text description**
- Layer Edge (1 of 3 from the top): Route 53, CloudFront, WAF.
- Layer Compute (2 of 3 from the top): API Gateway, Lambda, Step Functions.
- Layer Data (3 of 3 from the top): DynamoDB, S3, RDS.
- Aside Operations, beside the layers: CloudWatch, IAM.
- Link: CloudFront to API Gateway: HTTPS.
- Link: API Gateway to Lambda.
- Link: Lambda to DynamoDB: reads and writes.
- Link: Lambda to S3.
- Link: Step Functions to RDS.

### Microservices, from the foundation up

With `direction=up` the first layer written is the foundation, at the bottom, so the picture reads from the stores up to the clients. Seven services do not fit one row at a readable width, so the layer wraps to two rows and the last row is centred. A link from a block in the upper row goes along the gap between the rows to the lane, so write the blocks that have links in the row nearest the layer they link to.

**Diagram source**

```diagram
layers "Microservices" direction=up desc="Clients reach services through a gateway, and each service owns its store. The first layer written is the foundation, at the bottom."

layer "Stores" tone=aqua icon=lucide:database {
  block pgo "Orders DB" icon=simple:postgresql
  block pgu "Users DB" icon=simple:postgresql
  block mongo "Catalog" icon=simple:mongodb
  block redis "Cache" icon=simple:redis
}

layer "Services" tone=violet icon=lucide:boxes {
  block orders "Orders" icon=lucide:package
  block users "Users" icon=lucide:users
  block catalog "Catalog" icon=lucide:book-open
  block pricing "Pricing" icon=lucide:receipt
  block notify "Notifications" icon=lucide:bell
  block search "Search" icon=lucide:search
  block reports "Reporting" icon=lucide:gauge
}

layer "Gateway" tone=blue icon=lucide:route {
  block gw "API gateway" icon=lucide:route span=2
  block bff "Web BFF" icon=lucide:monitor
}

layer "Clients" tone=neutral icon=lucide:smartphone {
  block web "Web" icon=simple:react
  block ios "iOS" icon=lucide:smartphone
  block partner "Partners" icon=lucide:plug
}

web -> bff
ios -> gw : "REST"
partner -> gw : "REST"
gw -> orders : "gRPC"
bff -> catalog
orders -> pgo
users -> pgu
catalog -> mongo
orders <-> pricing : "price check"
web --> pgo : "bypass"
```

*Picture: Microservices: 4 layers, 16 blocks, 10 links. Clients reach services through a gateway, and each service owns its store. The first layer written is the foundation, at the bottom.*

**Text description**
- Layer Clients (1 of 4 from the top): Web, iOS, Partners.
- Layer Gateway (2 of 4 from the top): API gateway, Web BFF.
- Layer Services (3 of 4 from the top): Orders, Users, Catalog, Pricing, Notifications, Search, Reporting.
- Layer Stores (4 of 4 from the top): Orders DB, Users DB, Catalog, Cache.
- Link: Web to Web BFF.
- Link: iOS to API gateway: REST.
- Link: Partners to API gateway: REST.
- Link: API gateway to Orders: gRPC.
- Link: Web BFF to Catalog.
- Link: Orders to Orders DB.
- Link: Users to Users DB.
- Link: Catalog to Catalog.
- Link: Orders and Pricing, both ways: price check.
- Link: Web to Orders DB: bypass, optional or indirect.

## The language

One statement to a line. Text is always in double quotes, options are `key=value`, and `#` starts a comment. Indentation is for the reader and means nothing to the parser: nesting comes from the braces.

| Statement | Meaning | Example |
|---|---|---|
| `layers "Title" direction=<down\|up> counts=<on\|off> desc="<text>"` | The header. Every option is optional, and so is the title. | `layers "Online banking" direction=down` |
| `desc "<text>"` | The plain-language description, as a statement. The same as `desc=` on the header. | `desc "Each layer calls the layer below it."` |
| `layer "<Name>" tone=<tone> icon=<icon> {` | Opens a layer. Write blocks inside it, and close it with `}` on its own line. | `layer "Domain" tone=violet icon=lucide:boxes {` |
| `block <id> "<Label>" sub="<note>" icon=<icon> tone=<tone> span=<1-6>` | A card in the layer or aside that is open. | `block pay "Payments" sub="Go" icon=lucide:credit-card span=2` |
| `aside "<Name>" tone=<tone> icon=<icon> {` | Opens a band on the right of the stack, with its own blocks stacked in it. | `aside "Security" icon=lucide:shield {` |
| `<from> -> <to> : "<label>"` | A link between two blocks of the layers. The label is optional. | `web -> pay : "REST"` |
| `}` | Closes the layer or aside that is open, on a line of its own. | `}` |

A block's id is the word a link names. It starts with a letter, may contain letters, digits, `_` and `-`, and is unique in the diagram. A link may be written before the blocks it joins.

```plotwork
layers "Shop"

layer "App" {
  block web "Storefront" icon=lucide:globe
  block api "API" sub="Checkout and catalog" icon=lucide:server
}

layer "Data" {
  block db "Database" icon=lucide:database
}

web -> api : "HTTPS"
api -> db : "SQL"
```

### Layers and tone

A layer takes the next tone of a rotation (`blue`, `violet`, `aqua`, `orange`, `amber`, `neutral`) unless `tone=` sets one, and its blocks keep the tone. A block may choose its own, to mark one block out. Choose tones for meaning, as the example does, and nobody writes a color value: the theme owns every color in light and dark. Green and red are left out of the rotation because they read as good and bad news; choose them with `tone=` when they should mean that.

### Blocks, columns, and spans

A layer divides its width into equal columns and puts one block in each, 12px apart. When the blocks fit one row with every word whole, they fill it, so a layer of three blocks has three wide cards. When they do not, the blocks wrap to the fewest rows, as even as the order allows, and a short last row is centred so its cards keep the width of the rows above. What a card holds, the icon and the text, is centred in it, so the room at its left and its right is the same.

`span=2` makes a block as wide as two columns, and the other blocks keep their one. A span can be at most 6, and never more columns than the layer has.

### Asides

An `aside` is a tall band beside the stack for what cuts across every layer: security, monitoring, delivery. It has a dashed edge, which says it cuts across, its own corner tab, and its blocks hang from the tab one under the other. It stands on the right of the stack, as wide as its widest block or its name needs, and as tall as the stack; if its blocks need more height than the layers, the layers grow to match. Two asides stand side by side. A block in an aside takes no links, and a link to one is an error that says why.

### Links

Links leave and enter the top and bottom of a block, so a line is never squeezed into the gap between two cards. Between layers that are next to each other a link runs along the gap between them. A link between two blocks of one row runs under the row, inside the layer. A link that skips a layer runs along the gap to a lane on the right of the stack, down the lane, and back along the gap above its target. Every line stays out of every block, and a link between blocks that stand over each other is straight.

| Arrow | Line | Heads | Say it for |
|---|---|---|---|
| `->` | solid | one, at the second block | A call or a flow of data. |
| `-->` | dashed | one | A reply, a read, or a link that is optional or indirect. |
| `<->` | solid | two | A two-way call. |
| `<-->` | dashed | two | A two-way link that is optional or indirect. |
| `..>` | dotted | one | An asynchronous call: an event, a webhook. |
| `--` | solid | none | An association with no direction. |

Keep the few links that show how the layers depend on each other. A layered picture with forty links is a flow diagram that wants to be one.

### Direction and counts

`direction=up` puts the first layer at the bottom, for a stack built on its foundation, and everything else reads the same. `counts=on` shows how many blocks each layer and aside holds, in a pill in its tab.

## Limits and warnings

| Limit | Value | What happens past it |
|---|---|---|
| Layers | 12 | `E140` before any layout, with the advice to merge layers. |
| Asides | 2 | `E140`. |
| Blocks, in the layers and asides together | 80 | `E140`. |
| Links | 80 | `E140`. |
| A span | 6 | `E136`. |
| Blocks in a layer before the density warning | 8 | `W200`. |
| Links before the density warning | 20 | `W200`. |

The picture is 880px wide, the width a Markdown column keeps text readable at, and grows taller with the rows and with the room the links need. The layout is plain arithmetic, so 80 blocks and 80 links are laid out in a few milliseconds.

A block is as wide as its column, and its label wraps to at most three lines, so a word is kept whole by giving its layer fewer, wider columns. Text is cut with an ellipsis, and `W203` says so, only when no column could hold the word. The full text is always in the text description of the picture.

Every mistake comes back as a result with a code, a line, a column, and a hint. Warnings never stop the drawing.

| Code | When it fires | Fix |
|---|---|---|
| `E100` | A line that does not start with a keyword or a block id, a layer or an aside with no name or no block, a block outside any layer, a layer inside another, a link from a block to itself, text after a statement, or a diagram with no layers. | Read the hint, which names the line to write. |
| `E101`, `E110`, `E121`, `E122`, `E123` | An unterminated string, an unknown statement, an unknown tone, an unknown icon, or an unknown direction. | Read the hint, which names the nearest choice. |
| `E130`, `E131` | A block id used twice, or a link to a block that does not exist or is in an aside. | Pick another id, or declare the block, or link a block of a layer. |
| `E133`, `E134` | A layer or an aside that is never closed, or a `}` with nothing to close. | Add or remove the brace. |
| `E135`, `E136` | An unknown option, or a bad value for `counts=` or `span=`. | Read the hint, which says what to write. |
| `E140` | More than 12 layers, 2 asides, 80 blocks, or 80 links. | Merge layers, fold blocks, or split the diagram. |
| `W200` | A layer with no blocks, a layer with more than 8 blocks, or more than 20 links. | Fill or remove the layer, split a crowded layer in two, or keep the links that matter. |
| `W202` | The picture is wider than a Markdown column and its text would shrink. | Shorten the labels. |
| `W203` | A block label, a tab name, or a link label was cut with an ellipsis. | Shorten it, or move detail into `sub=`. |

## From Mermaid

Mermaid `block-beta` is not read yet. It draws a grid of blocks with no layers and no meaning for a layer, so no mapping from it to a layered picture is right, and the converter reports it as a type it does not draw. Two things work today. Describe the system to your agent, or paste the Mermaid and say which blocks are tiers, and ask it to draw the layers; that is a better picture than a converted grid. If the Mermaid is a `flowchart` with `subgraph` blocks for the tiers, it converts to a flow diagram with a group for each tier, which keeps the arrows between the boxes. The [migration guide](https://docs.plotwork.dev/migrating-from-mermaid.md) covers the converter in full.

```text
Use Plotwork to draw this Mermaid block diagram as a layered architecture. Make each row a layer, name the layers from what is in them, and keep the arrows as links between blocks.
```

## See also

| Doc | Why |
|---|---|
| [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The flow, sequence and schema languages, and the rules every kind shares |
| [Examples to copy](https://docs.plotwork.dev/examples.md) | Finished diagrams with the prompt that gets an agent to write each one |
| [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Checked cloud and network patterns an agent can fill in, when the picture is how traffic flows |
| [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Accounts, regions and networks as nested groups, when the picture is where things run |
| [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | Convert existing Mermaid diagrams, and what is dropped |
| [Language reference](https://docs.plotwork.dev/reference/language.md) | Every kind, node type, option and diagnostic |
| [Diagram kinds](https://docs.plotwork.dev/reference/language/kinds.md) | The kinds Plotwork draws and the header of each |
