How Draftsman draws every Eloquent relationship type, illustrated with a small team-management application.
Edges connect the actual columns involved: the foreign key row on one card to the key row it references on the other. With crow's-foot notation enabled, the end caps carry the semantics — a bar for one, a fork for many, a circle for optional. Pivot and intermediate tables are drawn as first-class cards, and Draftsman suppresses shortcut edges whenever the path is already visible through such a card — so a many-to-many reads as what it is at the schema level: two one-to-many relationships meeting at a pivot.
Each section links to the relevant chapter of the Laravel documentation.
hasOne
A User has one Profile; the profiles table
carries the user_id foreign key.
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
belongsTo
The child side of a one-to-one or one-to-many: the model holding the foreign key points
back at its parent. Here a User belongs to their current
Team through the current_team_id column — note the optional
(circle) cap, since the column is nullable.
public function currentTeam(): BelongsTo
{
return $this->belongsTo(Team::class, 'current_team_id');
}
Laravel docs: One to Many (Inverse) / Belongs To
hasMany
A Team has many TeamInvitations. The fork cap on the
invitation side reads "many": one team row, any number of invitation rows.
public function invitations(): HasMany
{
return $this->hasMany(TeamInvitation::class);
}
belongsToMany
A Team has many members and a User belongs to many teams,
joined through the team_members pivot. This app promotes the pivot to a
real model (Membership, via using()), so Draftsman draws it
as a card of its own — and the many-to-many appears as what it is in the schema: each
pivot row pointing at one user and one team.
public function members(): BelongsToMany
{
return $this->belongsToMany(User::class, 'team_members', 'team_id', 'user_id')
->using(Membership::class)
->withPivot(['role']);
}
hasOneThrough
A Membership reaches one Profile through the
User it belongs to. Draftsman shows the path itself: membership to user,
user to profile.
public function userProfile(): HasOneThrough
{
return $this->hasOneThrough(Profile::class, User::class, 'id', 'user_id', 'user_id', 'id');
}
hasManyThrough
A User reaches the teams they own through their Membership
rows. The picture is the same two-hop path a many-to-many makes — the through model in
the middle carries a foreign key to each end.
public function ownedTeams(): HasManyThrough
{
return $this->hasManyThrough(
Team::class,
Membership::class,
'user_id',
'id',
'id',
'team_id',
)->where('team_members.role', TeamRole::Owner->value);
}
Laravel docs: Has Many Through
morphOne
A Team has one Note, but notes can belong to
more than one kind of parent: the notable_type column (marked with a
tilde) records which model each note belongs to, and notable_id points at
it.
public function note(): MorphOne
{
return $this->morphOne(Note::class, 'notable');
}
Laravel docs: One to One (Polymorphic)
morphMany
The same notes table serves a one-to-many from User: a user
has many notes through the identical notable columns.
public function notes(): MorphMany
{
return $this->morphMany(Note::class, 'notable');
}
Laravel docs: One to Many (Polymorphic)
morphTo
From the child's side, Note::notable() resolves to whichever model the
type column names. On the canvas that is one child with an edge to every parent it can
reach — here both User and Team.
public function notable(): MorphTo
{
return $this->morphTo();
}
Laravel docs: Polymorphic Relationships
morphToMany
Tags attach to teams (or anything else) through the taggables pivot, whose
taggable_type column records what each row is attached to. Draftsman draws
the generic pivot as a card, so the polymorphic many-to-many reads just like the plain
one — two paths meeting at a pivot. The inverse side is declared with
morphedByMany.
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
// The inverse, on Tag:
public function teams(): MorphToMany
{
return $this->morphedByMany(Team::class, 'taggable');
}