Skip to content

Portable Core and Backend Extensions

c4-diagrams separates portable C4 model data from backend-specific rendering hints.

The portable core includes C4 elements, boundaries, concrete diagram types, and the generic Rel relationship. Code that stays within that subset can be rendered by different backends, subject to each backend's supported C4 syntax.

Backend-specific features are passed with explicit backend kwargs, such as plantuml= or mermaid=. Use those kwargs for data that only one renderer understands, while keeping the portable core free of renderer-specific hints.

Example: PlantUML-specific features

PlantUML directional relationships, bidirectional relationships, layout helpers, dynamic indexes, sprites, links, tags, and similar hints are convenience DSL for PlantUML rendering. They are not portable C4 model data.

Renderers validate extension data before rendering. By default, foreign extension data is ignored: PlantUML consumes plantuml={...} data and Mermaid consumes mermaid={...} data. Enable strict extension validation when a diagram must fail fast if it contains extension data owned by another backend.

Portable core example

This diagram uses only portable C4 elements and Rel, so it can be rendered to either PlantUML or Mermaid source:

from c4 import Person, Rel, System, SystemContextDiagram


with SystemContextDiagram("Order Processing") as diagram:
    customer = Person("Customer", "Places orders")
    order_system = System("Order System", "Processes customer orders")

    customer >> Rel("Places orders using") >> order_system


plantuml_source = diagram.as_plantuml()
mermaid_source = diagram.as_mermaid()

PlantUML-specific DSL

Directional relationship shortcuts and layout helpers live in c4.contrib.plantuml because they map to PlantUML/C4-PlantUML behavior:

from c4 import Container, ContainerDiagram, Person, System
from c4.contrib.plantuml import LayRight, RelRight


with ContainerDiagram("Order Processing") as diagram:
    customer = Person("Customer")
    order_system = System("Order System")
    api = Container("API", "Accepts order requests", technology="FastAPI")
    database = Container("Database", "Stores orders", technology="PostgreSQL")

    customer >> RelRight("Uses") >> api
    api >> RelRight("Reads and writes") >> database

    LayRight(api, database)

Backend extension kwargs

Use backend kwargs for renderer-specific element and relationship metadata. The data is stored in the element's extensions mapping under the backend name:

from c4 import ContainerDb, ContainerDiagram


with ContainerDiagram("Order Processing") as diagram:
    orders = ContainerDb(
        "Orders Database",
        "Stores order state",
        technology="PostgreSQL",
        plantuml={"tags": ["storage"]},
    )


assert orders.extensions == {"plantuml": {"tags": ["storage"]}}

Compatibility envelope

The same extension data can be provided through extensions=:

from c4 import ContainerDb, ContainerDiagram


with ContainerDiagram("Order Processing") as diagram:
    orders = ContainerDb(
        "Orders Database",
        "Stores order state",
        technology="PostgreSQL",
        extensions={"plantuml": {"tags": ["storage"]}},
    )


assert orders.extensions == {"plantuml": {"tags": ["storage"]}}