D2 Options¶
The D2 backend supports render options through
D2RenderOptions and
D2RenderOptionsBuilder.
from c4 import SystemContextDiagram
from c4.renderers import D2RenderOptions, RenderOptions
options = D2RenderOptions(
direction="down",
layout="elk",
theme=300,
title_near="top-center",
sequence_diagram=True,
auto_number_relationships=True,
include_type_label=False,
include_technology=False,
include_properties=True,
)
with SystemContextDiagram("Example", render_options=RenderOptions(d2=options)) as diagram:
...
diagram.save_as_d2("diagram.d2")
You can pass render_options directly to
D2Renderer:
from c4.renderers import D2Renderer
renderer = D2Renderer(render_options=options)
D2 render options can also be set via RenderOptions.
You can also use the builder:
from c4.renderers import D2RenderOptionsBuilder
options = (
D2RenderOptionsBuilder()
.direction("down")
.layout("elk")
.title_near("bottom-center")
.sequence_diagram()
.auto_number_relationships()
.include_properties()
.bidirectional_relationships("single_edge")
.build()
)
Option Reference¶
| Option | Default | Behavior |
|---|---|---|
direction |
"right" |
Emits the D2 direction directive. Set None to omit it. |
layout |
"dagre" |
Selects the D2 CLI layout engine for image export. Use "dagre" or "elk". |
theme |
None |
Emits a D2 theme ID when set. |
title_near |
"top-center" |
Emits diagram titles as Markdown nodes positioned with D2 near. Set None to render the title node without forced placement. |
sequence_diagram |
False |
Emits shape: sequence_diagram for DynamicDiagram output. |
auto_number_relationships |
False |
Prefixes relationship labels with their render order, such as 1. Uses. |
include_type_label |
True |
Includes C4 element type labels, such as Software System or Container, in Markdown labels. |
include_technology |
True |
Includes element technology in visible labels. |
include_properties |
False |
Emits element and relationship properties as Markdown tables. |
bidirectional_relationships |
"two_edges" |
Renders bidirectional relationships as two directed edges or one <-> edge. |
fully_qualified_relationships |
True |
Uses fully qualified D2 paths for relationship endpoints. |
legend |
None |
Emits a structured D2 legend under vars.d2-legend. |
Direction¶
direction controls the overall D2 layout direction and accepts "up",
"down", "left", "right", or None. The default is "right". Use
None when you want D2 or the selected layout engine to choose without an
explicit source directive.
D2 element and boundary extensions also accept d2={"direction": ...} for a
local container direction. See D2 extensions.
Layout Engine¶
layout controls the D2 layout engine used for image export through the local
D2 CLI. It accepts "dagre" or "elk" and defaults to D2's default Dagre
layout engine. This option is not emitted into .d2 source output; it is
applied when rendering bytes or files with a D2 backend.
options = D2RenderOptions(layout="elk")
When exporting with the CLI, --d2-layout overrides any layout set in
D2RenderOptions, including render options attached directly to the diagram.
Theme¶
theme emits a D2 theme ID when set to a non-negative integer. Leave it as
None to use D2's default theme selection.
options = D2RenderOptions(theme=300)
See the upstream D2 themes documentation for available theme IDs and behavior.
Titles¶
Diagram titles are emitted as generated Markdown text nodes instead of a
top-level title: directive. By default, the renderer uses the reserved
identifier __title and places it with near: top-center.
__title: ||md
# Internet Banking Context
|| {
near: top-center
}
title_near accepts top-left, top-center, top-right, center-left,
center-right, bottom-left, bottom-center, or bottom-right. Set
title_near=None to keep the generated title node while leaving placement to
D2.
Dynamic Diagrams¶
sequence_diagram=True emits shape: sequence_diagram for
DynamicDiagram output. It is intended for dynamic diagrams where relationship
order matters. Other diagram types keep their normal D2 shape.
auto_number_relationships=True prefixes relationship labels with their render
order, such as 1. Uses and 2. Calls. This can be used with or without
sequence_diagram.
Labels¶
include_type_label controls whether D2 Markdown labels include the C4 element
type, such as [Software System] or [Container: Python]. It defaults to
True. Set it to False when the type should remain in the model but not
appear in the rendered diagram.
include_technology controls whether element technology appears in visible
labels. It defaults to True, so a container created with
technology="Python" includes that technology in the D2 label. Set it to
False when technology should remain in the model but not appear in the
rendered diagram.
Properties¶
include_properties controls whether portable C4 properties are rendered in D2
output. When include_properties=True, D2 emits element and relationship
properties as Markdown tables in labels. When include_properties=False,
property tables are omitted.
Elements with descriptions may still use Markdown labels, but their
properties block is not rendered until properties are enabled. See the shared
Properties page for the property API.
Boundaries with Markdown labels are emitted with shape: rectangle by default
so the container border remains visible in D2 output. Explicit boundary
extensions still win, so d2={"shape": "cloud"} keeps shape: cloud.
Bidirectional Relationships¶
bidirectional_relationships controls how bidirectional relationships are
emitted:
| Value | Behavior |
|---|---|
"two_edges" |
Render the relationship as two directed D2 edges. This is the default. |
"single_edge" |
Render one D2 <-> edge. |
Use "two_edges" when downstream tooling expects separate directed edges. Use
"single_edge" when the D2 source should show a compact bidirectional edge.
Relationship Paths¶
fully_qualified_relationships=True makes relationship endpoints use complete
D2 paths, including parent containers. This is the safest default for nested
boundaries and deployment nodes because endpoint names remain unambiguous.
Set fully_qualified_relationships=False only when you need shorter D2 source
and your diagram does not rely on nested endpoint disambiguation.
Legends¶
D2 legends are represented by D2RenderOptions.legend. The renderer emits them
under vars.d2-legend, matching D2's structured legend support.
from c4.renderers import (
D2Legend,
D2LegendElement,
D2LegendRel,
D2RenderOptions,
)
options = D2RenderOptions(
legend=D2Legend(
items=[
D2LegendElement(
label="External system",
shape="rectangle",
style={"stroke_dash": 5},
),
D2LegendRel(
label="Synchronous call",
style={"stroke": "#555555"},
hide_endpoints=True,
),
],
),
)
Legend identifiers are generated by default and scoped to vars.d2-legend.
You can provide explicit aliases when you need stable legend item identifiers.
For relationship legend items, hide_endpoints=True is the structured
equivalent of setting helper endpoint opacity to zero in raw D2.
See the upstream D2 legend documentation for the raw D2 syntax.