Language Reference
Full syntax — every block, field, and diagnostic code
Language Reference
Complete syntax reference for the Datro DSL (.dtro files). Every block type, every field, every diagnostic code — all in one place.
Magic header
Every .dtro file must begin with the magic header on line 1. The parser returns E100 if it is missing or malformed.
#!DATRO 1.0meta block
Optional document-level metadata. Produces I003 info hint if omitted. All fields are optional strings/arrays except kind.
#!DATRO 1.0
meta {
title: "Payment Service"
version: "2.1.0"
description: "Handles card and crypto charges"
author: "jane@example.com"
owner: "payments-team"
tags: ["payments", "critical"]
sla: "99.95%"
criticality: "P0"
kind: architecture
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | No | — | Human-readable diagram title |
version | string | No | — | Semantic version of the diagram |
description | string | No | — | Free-text description |
author | string | No | — | Author name or email |
owner | string | No | — | Team or person responsible |
tags | string[] | No | — | Arbitrary classification tags |
sla | string | No | — | Service-level objective, e.g. `"99.9%"` |
criticality | string | No | — | Criticality tier: `"P0"` | `"high"` | `"medium"` | `"low"` |
kind | DiagramKind "architecture""mindmap""state""sequence""algorithm" | No | architecture | Diagram variant |
node block
Declares an entity in the diagram. Each node has a unique id, a semantic type that controls its rendered shape and default colour, and an optional attrs block for metadata.
# Minimal
node api_gw { type: gateway label: "API Gateway" }
# With attrs block
node payment_svc {
type: service
label: "Payment Service"
attrs {
tech: "nestjs"
owner: "payments-team"
deprecated: false
sla: "99.95%"
}
}
# Inline attrs (alternative syntax)
node db { type: database label: "Payments DB" attrs { tech: "postgres" } }| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | identifier | Yes | — | Unique node identifier — alphanumeric + underscore |
type | NodeType "service""database""queue""cache""gateway""user""actor""system""external""process""storage""function""broker""scheduler""load_balancer""cdn""dns""firewall""client""mobile""web""iot""unknown" | No | unknown | Semantic node category |
label | string | No | (node id) | Display text shown in the diagram |
group | identifier | No | — | Group id this node belongs to (alternative to `group.members`) |
attrs | AttrMap | No | — | Arbitrary key-value metadata — strings, numbers, booleans, or arrays |
23 node types
All 23 values: `service` `database` `queue` `cache` `gateway` `user` `actor` `system` `external` `process` `storage` `function` `broker` `scheduler` `load_balancer` `cdn` `dns` `firewall` `client` `mobile` `web` `iot` `unknown`.
edge block
Declares a relationship between two nodes. Edge direction is written directly in the declaration: -> (directed), <-> (bidirectional), or -- (undirected).
# Minimal
edge api_gw -> payment_svc { kind: http }
# Full metadata
edge payment_svc -> stripe {
kind: http
label: "charge API"
weight: 2.0
async: false
attrs { sla: "200ms" }
}
# Bidirectional edge
edge cache <-> service { kind: read }
# Undirected edge
edge svc_a -- svc_b { kind: depends }| Field | Type | Required | Default | Description |
|---|---|---|---|---|
from | identifier | Yes | — | Source node id |
direction | EdgeDir "->""<->""--" | Yes | — | Arrow type written inline |
to | identifier | Yes | — | Target node id |
kind | EdgeKind "http""grpc""graphql""websocket""tcp""udp""query""event""publish""subscribe""stream""call""read""write""deploys""monitors""depends""extends""implements""triggers""unknown" | No | unknown | Semantic relationship type — drives colour and style |
label | string | No | — | Short text displayed on the edge line |
weight | number | No | 1.0 | Relative traffic weight — heavier edges render thicker |
async | boolean | No | false | When `true`, edge renders as dashed (fire-and-forget) |
attrs | AttrMap | No | — | Arbitrary metadata — passed through to JSON IR and tooltips |
21 edge kinds
All 21 values: `http` `grpc` `graphql` `websocket` `tcp` `udp` `query` `event` `publish` `subscribe` `stream` `call` `read` `write` `deploys` `monitors` `depends` `extends` `implements` `triggers` `unknown`.
group block
Clusters related nodes into a labelled subgraph box. A node may belong to at most one group — E403 fires on conflicts. Groups can be nested: a member id may reference another group id.
group data_tier {
label: "Data Layer"
members: [payments_db, fraud_cache, audit_log]
attrs { compliance: "PCI-DSS" }
}
# Alternative: assign group on the node
node payments_db {
type: database
label: "Payments DB"
group: data_tier
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | identifier | Yes | — | Unique group identifier |
label | string | No | (group id) | Display label for the subgraph box |
members | identifier[] | No | [] | Node or group ids to include — takes precedence over `node { group: X }` |
attrs | AttrMap | No | — | Arbitrary metadata |
constraint block
Declares an architectural rule that the validator evaluates against the IR. Violations produce E501 (or W501 when severity: warn). The rule field uses a concise three-token grammar.
# Prohibition: databases may not call external services
constraint {
id: "no-db-external"
rule: "database !-> external"
message: "Databases must not connect directly to external APIs"
severity: error
}
# Requirement: every gateway must connect to a service
constraint {
rule: "gateway -> service"
message: "Each gateway must route to at least one service"
severity: warn
}
# Membership: all databases must be in the data_tier group
constraint {
rule: "database in data_tier"
message: "All database nodes must belong to the data_tier group"
severity: error
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | No | — | Optional human-readable rule identifier |
rule | string | Yes | — | `SUBJECT VERB OBJECT` — see verb table below |
message | string | No | — | Custom message shown in diagnostics when the rule is violated |
severity | ConstraintSeverity "error""warn" | No | error | Diagnostic severity when violated |
Constraint verbs
- `!->` — no
SUBJECTnode may have a directed edge to anyOBJECTnode. - `->` — every
SUBJECTnode must have at least one directed edge to anOBJECTnode. - `!<->` — no bidirectional edge may exist between
SUBJECTandOBJECTnodes. - `<->` — every
SUBJECTnode must have a bidirectional edge to someOBJECTnode. - `in` — every
SUBJECTnode must be a member of theOBJECTgroup. - `!in` — no
SUBJECTnode may be a member of theOBJECTgroup.
Subject and object tokens
Each token can be a **node type** (`database`, `external`, …), a **group id** (e.g. `data_tier`), or `*` (wildcard matching every node).
import block
Splits large diagrams across multiple .dtro files. The resolver merges imported IR into the parent before validation. Use as for namespacing and expose to whitelist specific ids.
# Import everything from another .dtro file
import "auth.dtro"
# Import with a namespace alias — IDs become auth.user_service, etc.
import "auth.dtro" as auth
# Import only specific IDs (whitelist)
import "infra.dtro" as infra expose [load_balancer, cdn]| Field | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | Yes | — | Relative or absolute path to the `.dtro` file |
as | identifier | No | — | Namespace alias — all imported ids are prefixed `alias.id` |
expose | identifier[] | No | — | Whitelist — only these ids are merged into the parent IR |
layout block
Hints to the renderer about how to arrange the graph. All fields are optional — the renderer uses sensible defaults when the block is omitted.
layout {
direction: LR # LR | RL | TB | BT
algo: dagre # dagre | dot | elk | force | none
node_sep: 60 # px between nodes in the same rank
rank_sep: 120 # px between ranks
margin: 20 # graph margin in px
cluster_padding: 16 # padding inside group boxes
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
direction | LayoutDirection "LR""RL""TB""BT" | No | LR | Graph flow direction |
algo | LayoutAlgo "dagre""dot""elk""force""none" | No | dagre | Layout algorithm |
node_sep | number | No | 60 | Minimum pixels between adjacent nodes in the same rank |
rank_sep | number | No | 120 | Minimum pixels between ranks |
margin | number | No | 20 | Graph margin in pixels |
cluster_padding | number | No | 16 | Extra padding inside group bounding boxes |
theme block
Controls the visual appearance of the rendered diagram. The palette maps node types to hex colours. The accessibility sub-block enables automatic WCAG 2.1 contrast checking.
theme {
name: "Corporate"
palette {
service: "#D0E8FF"
database: "#FFE8C0"
gateway: "#FFD0D0"
queue: "#E8D0FF"
cache: "#C0FFE8"
external: "#F0F0F0"
background: "#FFFFFF"
}
typography {
font_family: "Inter, sans-serif"
node_font_size: 11
edge_font_size: 9
label_font_weight: "600"
}
accessibility {
min_contrast: AA # AA | AAA | none
max_label_length: 60
}
}Auto font colour
The DOT exporter automatically picks black or white text for each node fill using the WCAG 2.1 relative luminance formula. You never need to set a separate text colour.
style block
Fine-grained per-element overrides applied after the theme. Rules cascade: by_id overrides by_type. Each sub-block maps a key to a property map.
style {
# Override by node type
by_type {
database { fill: "#FFF3CD" stroke: "#E6A817" shape: cylinder }
external { fill: "#F8F9FA" stroke: "#6C757D" dash: true }
}
# Override individual node by id
by_id {
stripe { fill: "#635BFF" stroke: "#4B44CC" }
}
# Style edge kinds
by_edge_kind {
query { color: "#E6A817" width: 2 }
publish { color: "#198754" dash: true }
}
# Style edges that carry a boolean attr set to true
by_edge_attr {
async { dash: true color: "#ADB5BD" }
}
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
by_type | Record<NodeType, StyleProps> | No | — | Styles applied to all nodes of a given type |
by_id | Record<id, StyleProps> | No | — | Styles applied to a specific node id — highest priority |
by_edge_kind | Record<EdgeKind, StyleProps> | No | — | Styles applied to all edges of a given kind |
by_edge_attr | Record<attrName, StyleProps> | No | — | Styles applied to edges where the named boolean attr is `true` |
Diagnostic codes
All diagnostics include a code, severity, message, and an optional line:col location. The table below lists every code the toolchain can emit.
Errors (E×××)
- `E100` — Magic header
#!DATRO 1.0is missing or malformed - `E201` — Duplicate node id
- `E202` — Node references an unknown group id
- `E301` — Edge
fromnode not found - `E302` — Edge
tonode not found - `E303` — Edge
weightis negative - `E401` — Duplicate group id
- `E402` — Group
membersreferences an unknown id - `E403` — Node is assigned to more than one group
- `E404` — Circular group nesting detected
- `E500` — Constraint rule has a syntax error
- `E501` — Constraint rule is violated (severity mirrors the constraint's own
severity)
Warnings (W×××)
- `W301` — Duplicate edge (same from/to/kind/label)
- `W501` — Constraint rule uses an unknown verb
- `W601` — Node label exceeds
max_label_length(default 80 chars) - `W602` — Theme palette colour falls below WCAG AA contrast (4.5:1)
- `W603` — Theme palette colour falls below WCAG AAA contrast (7.0:1)
Info hints (I×××)
- `I001` — Isolated node — no edges connect to it
- `I002` — Empty group — no members declared
- `I003` — No
metablock — recommended for documentation - `I601` — No theme palette defined — contrast checks skipped
Next steps
- Use the CLI Reference to validate, format, and export from the command line.
- Embed diagrams in Markdown with the Markdown plugin.
- Go back to Quick Start for a step-by-step walkthrough.
Try the full language live — paste any snippet above and validate instantly.