Browse documentationOpen

byrcsc/laravel-cartographer · 1.x

Quick start.

Generate a full diagram for a blog application, read what came out, scope it down to one subsystem, and commit both files.

This walkthrough generates two diagrams for a small blog application: a full one for the repository, and a focused one for the publishing subsystem. It assumes the package is installed and the database is migrated.

The example application has eight models under app/Models: User, Profile, Category, Post, Comment, Tag, Attachment, and AuditLog. Every output below is what those models and their schema actually produce.

1. Generate the full diagram

php artisan cartographer:erd
   INFO  Generated 9 entities and 18 edges. Written to [/srv/blog/docs/erd.md].

Nine entities from eight models: the extra one is post_tag, the pivot table behind Post::tags(). Pivot tables are pulled in because the diagram would otherwise hide a real table.

Open docs/erd.md and GitHub renders it as a diagram. The file starts with a generation marker and a mermaid fence:

<!-- Generated by byrcsc/laravel-cartographer. -->

```mermaid
erDiagram
    attachments }o--|| comments : "attachable (morph)"
    attachments }o--|| posts : "attachable (morph)"
    categories }o--|| categories : "parent"
    categories ||--o{ categories : "children"
    categories ||--o{ posts : "posts"
    comments ||--o{ attachments : "attachable (morph)"
    comments }o--|| posts : "post"
    comments }o--|| users : "author"
    posts ||--o{ attachments : "attachable (morph)"
    posts }o--|| categories : "category"
    posts ||--o{ comments : "comments"
    posts }o--o{ tags : "tags"
    posts }o--|| users : "author"
    profiles }o--|| users : "user"
    tags }o--o{ posts : "posts"
    users ||--o{ comments : "comments"
    users ||--o{ posts : "posts"
    users ||--o| profiles : "profile"
```

Four things in that block are worth reading closely.

posts }o--|| users : "author" is labelled with the relation method name, not the table name. Post::author() points at User through author_id, and the label says author because that is what the code calls it.

categories }o--|| categories : "parent" is a self-reference. It is drawn like any other edge.

attachable (morph) appears on both sides of the polymorphic relation. The label is the morph name, and Attachment::attachable() produces one edge per model that declares the inverse — here Post and Comment.

Both directions of a relation appear when both sides declare it. posts ||--o{ comments : "comments" and comments }o--|| posts : "post" are the same foreign key, seen from each model. Cartographer does not collapse them, because one side going missing is usually worth noticing.

2. Read the entity blocks

Below the edges, each entity lists its columns:

    posts {
        bigint author_id FK
        text body "nullable"
        bigint category_id FK "nullable"
        timestamp created_at "nullable"
        bigint id PK
        timestamp published_at "nullable"
        varchar title
        timestamp updated_at "nullable"
    }

Columns are sorted by name, not by their position in the table. Types are normalized, so bigint means the same thing whether the database is MySQL, PostgreSQL, or SQLite.

PK, FK, and UK come from the database, not from the models: primary keys, foreign key constraints, and unique indexes. A schema with no foreign key constraints produces no FK markers even when the relations are correct.

3. Cut it down to one subsystem

The full diagram is the reference. For a page about publishing, start from Post and follow one hop:

php artisan cartographer:erd \
  --models=Post \
  --depth=1 \
  --columns=keys \
  --output=docs/erd-publishing.md
   INFO  Generated 7 entities and 16 edges. Written to [/srv/blog/docs/erd-publishing.md].

Seven entities instead of nine. profiles is two hops away through User, and audit_logs has no relations at all, so neither is included.

--columns=keys drops everything that is not a primary key, a foreign key, or unique:

    posts {
        bigint author_id FK
        bigint category_id FK
        bigint id PK
    }
    tags {
        bigint id PK
        varchar name UK
    }

One detail that surprises people: posts.category_id still carries its FK marker even in a scope where categories was excluded. Columns describe the table, not the diagram.

4. Choose how much detail to show

Three column modes, from most to least detail:

ModeShowsGood for
allEvery column, with "nullable" where it appliesThe committed reference diagram
keysPrimary, foreign, and unique columns onlySubsystem diagrams
noneEntity names and edges onlyA map of the relationships

none is the one to reach for when the question is "what talks to what":

erDiagram
    categories ||--o{ posts : "posts"
    comments }o--|| posts : "post"
    posts }o--o{ tags : "tags"
    posts }o--|| users : "author"

    categories
    comments
    posts
    tags
    users

5. Preview before writing

--stdout prints the diagram instead of writing a file, and suppresses the warnings and the summary line so the output pipes cleanly:

php artisan cartographer:erd --models=Post --depth=1 --stdout
php artisan cartographer:erd --stdout --format=mmd > /tmp/erd.mmd

Use --format=mmd for a raw Mermaid file with no Markdown wrapper. That is the input mmdc and most Mermaid tooling expects.

6. Commit both files

git add docs/erd.md docs/erd-publishing.md
git commit -m "docs: add generated ERDs"

Both files are deterministic. Regenerate without changing the application and git status stays clean, which is what makes the next step useful.

7. Regenerate after a migration

php artisan migrate
php artisan cartographer:erd
git diff docs/erd.md

The diff shows exactly what the migration did to the diagram — a new column, a new edge, a changed type. That diff belongs in the same pull request as the migration.

To stop having to remember, see keeping the diagram current for a Composer script and a git hook, and continuous integration for a build that fails when the committed diagram is stale.