Mastering Logistics Data Models: An International Freight Forwarding ERD with PlantUML

In the complex ecosystem of international trade, the efficiency of freight forwarding operations hinges on the precision of data management. Logistics providers must orchestrate a symphony of moving parts—shippers, carriers, customs brokers, and regulatory bodies—while ensuring that every document, from the Bill of Lading to the Customs Declaration, is accurately linked to the physical cargo. A breakdown in this data chain can lead to costly delays, compliance penalties, and lost revenue. To navigate this complexity, architects and database designers rely on robust Entity-Relationship Diagrams (ERDs) to visualize the intricate web of relationships that govern the flow of goods across borders.

Real-world system context and operational workflow illustration

Visualizing these data structures is no longer a luxury; it is a necessity for building scalable logistics platforms. When managing customs declarations and bills of lading, the relationships between entities like Shipment, Carrier, Port, and CustomsBroker define the operational reality of the system. By adopting a diagram-as-code approach using PlantUML within VPasCode, teams can treat their database architecture as living documentation. This method allows for instant iteration, version-free collaboration via code sharing, and immediate visualization of how a change in one entity, such as adding a new Invoice field, ripples through the entire data model. This tutorial walks you through constructing a production-grade ERD that captures the full lifecycle of an international shipment, from booking to final delivery.

Understanding the Model: Purpose, Scope & Problem Framing

Diagram Abstraction & Representation

An Entity-Relationship Diagram (ERD) serves as the blueprint for relational databases, defining how data is stored, linked, and retrieved. In the context of this tutorial, the diagram abstracts the physical and administrative layers of freight forwarding. It models Entities as the core objects of the business (e.g., a Shipment or a CustomsDeclaration) and Relationships as the constraints and associations between them (e.g., a Carrier transports a Shipment). The notation used here—PlantUML’s Entity-Relationship syntax—allows us to define cardinalities (one-to-many, optional, mandatory) and attributes (primary keys, foreign keys) in a text-based format that is easily readable and maintainable.

Target Domain Scope & Scenario

This specific ERD focuses on the International Freight Forwarding domain, specifically addressing the critical junction of logistics and regulatory compliance. The scope is bounded by the following key scenarios:

  • Shipment Management: Tracking a cargo unit from origin to destination, including booking details, weight, volume, and status updates.
  • Document Lifecycle: Managing the generation and association of critical trade documents like the Bill of Lading (BoL) and Customs Declarations.
  • Regulatory Compliance: Linking shipments to Customs Brokers and tracking declaration statuses to ensure adherence to international trade laws.
  • Asset Tracking: Managing containers and individual cargo items within those containers, linking them to specific shipments.

By modeling these elements, the diagram provides a clear view of how a single Shipment acts as the central hub, connecting clients, carriers, ports, and the necessary paperwork.

Key Takeaways & Educational Insights

By following this guide, you will gain the ability to:

  • Structure a complex, multi-entity database schema for logistics using clean, text-based syntax.
  • Understand how to model one-to-many relationships (e.g., one Client to many Shipments) and optional relationships (e.g., a Shipment may or may not have a specific Container).
  • Apply best practices for naming conventions, primary/foreign key definition, and attribute typing in PlantUML.

Complete Diagram & Full Source Code

Before diving into the construction steps, here is the complete, rendered view of the International Freight Forwarding ERD. This diagram encapsulates the core data structures required to manage customs declarations and bills of lading.

Descriptive Alt Text

@startuml
!theme plain
title International Freight Forwarding - Customs Declaration & Bill of Lading Management ERD

/' 
Diagram Context:
This ERD models the core data structures for an international freight forwarding system
that manages customs declarations and bills of lading. It captures the relationships
between shipments, clients, carriers, customs brokers, ports, containers, and the
required trade documents. The diagram supports tracking of cargo movement,
regulatory compliance, and document lifecycle from origin to destination.
'/

' ==================== ENTITIES ====================

entity "Client" as client {
  * client_id : INT <<PK>>
  --
  company_name : VARCHAR
  contact_person : VARCHAR
  email : VARCHAR
  phone : VARCHAR
  address : TEXT
  tax_id : VARCHAR
  client_type : ENUM
}

entity "Shipment" as shipment {
  * shipment_id : INT <<PK>>
  --
  booking_number : VARCHAR
  shipment_type : ENUM
  origin_port_id : INT <<FK>>
  destination_port_id : INT <<FK>>
  departure_date : DATE
  arrival_date : DATE
  status : ENUM
  total_weight_kg : DECIMAL
  total_volume_cbm : DECIMAL
  client_id : INT <<FK>>
}

entity "BillOfLading" as bol {
  * bol_id : INT <<PK>>
  --
  bol_number : VARCHAR
  bol_type : ENUM
  issue_date : DATE
  place_of_issue : VARCHAR
  shipper_id : INT <<FK>>
  consignee_id : INT <<FK>>
  notify_party_id : INT <<FK>>
  shipment_id : INT <<FK>>
  freight_terms : ENUM
  incoterms : VARCHAR
  status : ENUM
}

entity "CustomsDeclaration" as customs {
  * declaration_id : INT <<PK>>
  --
  declaration_number : VARCHAR
  declaration_type : ENUM
  declaration_date : DATE
  customs_office : VARCHAR
  regime : VARCHAR
  total_duty_amount : DECIMAL
  currency : VARCHAR
  status : ENUM
  shipment_id : INT <<FK>>
  broker_id : INT <<FK>>
}

entity "CustomsBroker" as broker {
  * broker_id : INT <<PK>>
  --
  broker_name : VARCHAR
  license_number : VARCHAR
  contact_person : VARCHAR
  email : VARCHAR
  phone : VARCHAR
  address : TEXT
}

entity "Carrier" as carrier {
  * carrier_id : INT <<PK>>
  --
  carrier_name : VARCHAR
  scac_code : VARCHAR
  contact_person : VARCHAR
  email : VARCHAR
  phone : VARCHAR
  address : TEXT
  carrier_type : ENUM
}

entity "Port" as port {
  * port_id : INT <<PK>>
  --
  port_code : VARCHAR
  port_name : VARCHAR
  country : VARCHAR
  city : VARCHAR
  port_type : ENUM
}

entity "Container" as container {
  * container_id : INT <<PK>>
  --
  container_number : VARCHAR
  container_type : VARCHAR
  seal_number : VARCHAR
  tare_weight_kg : DECIMAL
  max_payload_kg : DECIMAL
  shipment_id : INT <<FK>>
}

entity "CargoItem" as cargo {
  * cargo_id : INT <<PK>>
  --
  description : TEXT
  hs_code : VARCHAR
  quantity : INT
  unit : VARCHAR
  weight_kg : DECIMAL
  volume_cbm : DECIMAL
  value : DECIMAL
  currency : VARCHAR
  shipment_id : INT <<FK>>
  container_id : INT <<FK>>
}

entity "Invoice" as invoice {
  * invoice_id : INT <<PK>>
  --
  invoice_number : VARCHAR
  invoice_date : DATE
  invoice_type : ENUM
  total_amount : DECIMAL
  currency : VARCHAR
  shipment_id : INT <<FK>>
  client_id : INT <<FK>>
}

entity "Document" as document {
  * document_id : INT <<PK>>
  --
  document_type : ENUM
  document_number : VARCHAR
  issue_date : DATE
  expiry_date : DATE
  file_path : VARCHAR
  shipment_id : INT <<FK>>
  customs_id : INT <<FK>>
  bol_id : INT <<FK>>
}

' ==================== RELATIONSHIPS ====================

client ||--o{ shipment : "books"
client ||--o{ invoice : "receives"
client ||--o{ bol : "shipper/consignee/notify"
shipment ||--o{ bol : "has"
shipment ||--o{ customs : "requires"
shipment ||--o{ container : "contains"
shipment ||--o{ cargo : "includes"
shipment ||--o{ invoice : "billed via"
shipment ||--o{ document : "has"
broker ||--o{ customs : "files"
carrier ||--o{ bol : "issues"
carrier ||--o{ shipment : "transports"
port ||--o{ shipment : "origin/destination"
container ||--o{ cargo : "holds"
customs ||--o{ document : "includes"
bol ||--o{ document : "includes"

@enduml

Step-by-Step Architectural Walkthrough

Building this ERD requires a structured approach, moving from high-level configuration to detailed entity definition, and finally to relationship mapping. We will break this down into four distinct phases.

Phase 1: Canvas Configuration & Layout Directives

Before defining any data structures, we must set the stage for the diagram. This involves initializing the PlantUML environment and selecting a visual theme that ensures readability. In our case, we use the !theme plain directive to create a clean, minimalistic look that focuses on the data structure rather than decorative elements.

We also define a global title to provide immediate context. This is crucial for documentation purposes, ensuring anyone viewing the diagram instantly understands its scope.


@startuml
!theme plain
title International Freight Forwarding - Customs Declaration & Bill of Lading Management ERD

/' 
Diagram Context: [Comment block describing the domain]
'/

Notice the comment block starting with /' and ending with '/. This allows us to embed rich, multi-line context directly into the code without affecting the rendering, serving as a permanent architectural note within the file.

Phase 2: Declaring Core Entities, Actors, and Boundaries

The heart of an ERD lies in its entities. In PlantUML, we define an entity using the entity keyword, followed by the display name and an alias for internal referencing. Each entity is enclosed in curly braces {} to hold its attributes.

Let’s examine the Client entity:


entity "Client" as client {
  * client_id : INT <>
  --
  company_name : VARCHAR
  contact_person : VARCHAR
  email : VARCHAR
  phone : VARCHAR
  address : TEXT
  tax_id : VARCHAR
  client_type : ENUM
}

Here, * client_id : INT <> defines the primary key. The * indicates the attribute is part of the key, and <> is a stereotype explicitly marking it as the Primary Key. The -- separator visually divides keys from regular attributes in the diagram. We apply this pattern to all core entities like Shipment, BillOfLading, and CustomsDeclaration, ensuring consistent data typing (e.g., INT, VARCHAR, DECIMAL, DATE).

Phase 3: Mapping Data Flows & Key Interactions

Once entities are defined, we must establish how they interact. This is where the logic of the logistics system is encoded. We use the | (one) and o{ (zero or many) symbols to define cardinality.

Consider the relationship between a Client and a Shipment:


client ||--o{ shipment : "books"

This line translates to: “One Client (mandatory) books Zero or Many Shipments.” The || on the left means the client is mandatory (a shipment cannot exist without a client), while o{ on the right means a client can have multiple shipments. We apply similar logic to other critical flows, such as shipment ||--o{ bol : "has" (One Shipment has Many Bills of Lading) and carrier ||--o{ shipment : "transports".

Phase 4: Grouping, Annotations & Visual Polish

While PlantUML ERD syntax is powerful, the final touch is ensuring the diagram is self-explanatory. We use relationship labels (the text in quotes, e.g., "books", "transports") to describe the nature of the link. This adds semantic meaning beyond just the structural link. For instance, port ||--o{ shipment : "origin/destination" clearly indicates that a port can be either the starting point or the endpoint of a shipment, a critical distinction in logistics routing.

Syntax & Keyword Deep Dive

To master this diagram, it is essential to understand the specific PlantUML ERD syntax elements used:

  • entity "Name" as alias { ... }: The fundamental building block. Name is what appears in the box, and alias is the short identifier used to define relationships.
  • * attribute : TYPE <>: Defines an attribute. The * prefix marks it as part of the Primary Key. The <> or <> stereotype visually renders the key type in the diagram.
  • --: A horizontal separator line used within the entity block to visually group keys separately from other attributes.
  • | and |{ / o{: Cardinality markers. | represents “One and Only One” (mandatory). o{ represents “Zero or More” (optional). | on the right side of ||--o{ means “One or More” (mandatory many).
  • : "Label": Defines the relationship label, describing the action or connection type (e.g., “files”, “transports”, “includes”).
  • /' ... '/: Multi-line comment syntax used to embed documentation directly within the code file.

Best Practices & Pitfalls to Avoid

When constructing complex ERDs for logistics or enterprise systems, adhering to best practices ensures your diagrams remain maintainable and clear.

  1. Consistent Naming Conventions: Always use singular nouns for entities (e.g., Shipment not Shipments) and snake_case for attributes (e.g., total_weight_kg). This improves readability and aligns with standard database design patterns.
  2. Explicit Foreign Keys: While PlantUML can infer relationships, explicitly marking foreign keys with <> in the entity definition helps database developers quickly identify the data integrity constraints.
  3. Modular Structure: If a diagram becomes too crowded, consider splitting it into multiple files (e.g., one for Shipment Management and one for Document Management) and using !include directives to link them. This prevents visual clutter.
  4. Avoid Redundant Relationships: Ensure relationships are necessary. For example, if Shipment already links to Carrier and Port, ensure you don’t create circular dependencies that confuse the data flow.

Start Building Logistics ERDs Faster with VPasCode

Design complex freight forwarding data models instantly in your browser with VPasCode, the free PlantUML editor that brings your database architecture to life without any installation.

Scroll to Top