Skip to content

Reusable Diagram Elements

For larger documentation sets, define frequently used people, systems, containers, and components once, then import them into each diagram as small element factories.

This keeps each diagram focused on the relationships it explains instead of repeating long element declarations. It also makes diagrams easier to maintain: when a shared system name, description, technology, or property changes, update the shared definition once.

Note

This page uses "elements" in the C4 sense: people, systems, containers, components, and related diagram objects. It does not refer only to Component elements.

Shared element library

Create a small Python package or module for reusable C4 elements. Each exported name is a factory that creates a fresh element instance in the active diagram context.

# architecture/elements.py
from functools import partial

from c4 import Person, System, SystemExt, with_properties


CustomerSupportAgent = with_properties(
    partial(
        Person,
        label="Customer Support Agent",
        description="Handles customer tickets and escalations.",
    ),
    ("Team", "Customer Support"),
)

SupportPortal = partial(
    System,
    label="Support Portal",
    description="Web application used to manage customer support cases.",
)

AuditedSupportPortal = with_properties(
    SupportPortal,
    ("Owner", "Customer Support"),
    ("Audit", "Enabled"),
)

TicketingService = partial(
    System,
    label="Ticketing Service",
    description="Stores tickets, comments, assignments, and status history.",
)

NotificationProvider = partial(
    SystemExt,
    label="Notification Provider",
    description="Sends email and SMS notifications to customers and agents.",
)

functools.partial pre-fills the bulky constructor arguments, while with_properties can add property rows to every element created by that factory.

Use shared elements in diagrams

Import the factories into a diagram file and call them inside the diagram context:

# diagrams/support_context.py
from c4 import Rel, SystemContextDiagram

from architecture.elements import (
    AuditedSupportPortal,
    CustomerSupportAgent,
    NotificationProvider,
    TicketingService,
)


with SystemContextDiagram("Customer support context") as diagram:
    support_agent = CustomerSupportAgent()
    support_portal = AuditedSupportPortal()
    ticketing_service = TicketingService()
    notification_provider = NotificationProvider()

    support_agent >> Rel("Reviews and updates tickets", "HTTPS") >> support_portal
    support_portal >> Rel("Creates and updates tickets", "REST API") >> ticketing_service
    ticketing_service >> Rel("Sends ticket status notifications") >> notification_provider

The resulting diagram file stays small because it describes only the elements that participate in this view and the relationships between them.

Organizing shared elements

For a small project, a single module is usually enough:

architecture/
  __init__.py
  elements.py

diagrams/
  support_context.py
  billing_context.py

For a larger documentation set, split the library by domain or C4 level:

architecture/
  __init__.py
  people.py
  systems.py
  containers.py
  components.py

diagrams/
  support_context.py
  billing_containers.py
  ticketing_components.py

Keep shared factories close to stable model facts: names, descriptions, technologies, aliases, tags, links, and property rows. Keep view-specific relationships in the diagram files so each diagram remains easy to read.

When to use this pattern

Use reusable element factories when the same person, system, container, or component appears in multiple diagrams, or when element declarations are large enough to distract from the diagram's purpose.

Inline declarations are still useful for one-off elements that appear in only one diagram. A shared library should make diagrams clearer, not hide important view-specific details.