Laravel Eloquent defines six primary relationship types: hasOnehasManybelongsTobelongsToManyhasManyThrough, and hasOneThrough. The foreign key always lives on the model that calls belongsTo, and on the pivot table when using belongsToMany. If you describe a relationship in plain English (“a User has many Posts”), LaraCopilot generates both the correct model methods and the migration with the right foreign key column in one session.

Eloquent Relationship Rules Every Laravel Developer Needs Saved

If You Have Ever Googled “where does the foreign key go in Laravel”

You are not alone and the confusion is structural, not a skill gap. Every relationship type has a different rule, and getting one wrong means a silent bug that only surfaces when you try to eager load data.

Every Eloquent Relationship Type Explained

hasOne — one parent, one child

Rule: One model owns exactly one related model. The foreign key lives on the related (child) model’s table, not on the parent.

Example: A User has one Profile. The profiles table holds the user_id foreign key.

Model:

// User.php
public function profile(): HasOne
{
    return $this->hasOne(Profile::class);
}

// Profile.php
public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}

Migration: Add user_id to the profiles table, not the users table.

$table->foreignId('user_id')->constrained()->cascadeOnDelete();

hasMany — one parent, many children

Rule: One model owns many related models. The foreign key lives on the child model’s table.

Example: A Post has many Comments. The comments table holds the post_id foreign key.

Model:

// Post.php
public function comments(): HasMany
{
    return $this->hasMany(Comment::class);
}

// Comment.php
public function post(): BelongsTo
{
    return $this->belongsTo(Post::class);
}

Migration: Add post_id to the comments table.

$table->foreignId('post_id')->constrained()->cascadeOnDelete();

belongsTo — the inverse of hasOne and hasMany

Rule: The model that calls belongsTo is the one that holds the foreign key. This is the inverse of both hasOne and hasMany.

Example: A Comment belongs to a Post. The comments table holds post_id.

// Comment.php
public function post(): BelongsTo
{
    return $this->belongsTo(Post::class);
}

When to use it: Any time a model has a foreign key column pointing to another model, define belongsTo on that model.

belongsToMany — many-to-many with a pivot table

Rule: Neither model holds the foreign key directly. Both foreign keys live in a separate pivot table.

Example: A Post belongs to many Tags, and a Tag belongs to many Posts. A post_tag pivot table holds post_id and tag_id.

Model:

// Post.php
public function tags(): BelongsToMany
{
    return $this->belongsToMany(Tag::class);
}

// Tag.php
public function posts(): BelongsToMany
{
    return $this->belongsToMany(Post::class);
}

Migration: Create a pivot table named with both model names in alphabetical order, singular, separated by an underscore.

Schema::create('post_tag', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
});

Extra pivot data: To store additional columns on the pivot table (e.g., assigned_at), add ->withPivot('assigned_at')->withTimestamps() to the relationship method.

hasManyThrough — access distant relationships

Rule: Used when model A relates to model C through model B. No pivot table. Model B holds A’s foreign key. Model C holds B’s foreign key.

Example: A Country has many Posts through Users. Users belong to a country. Posts belong to a user.

Model:

// Country.php
public function posts(): HasManyThrough
{
    return $this->hasManyThrough(Post::class, User::class);
}

When to use it: When you need to reach a model two relationships away without building a direct relationship.

Common confusion: hasManyThrough is not a many-to-many relationship. It is a chain of two one-to-many relationships.

hasOneThrough — single distant model

Rule: The same pattern as hasManyThrough, but used when the end result is a single model rather than a collection.

Example: A Mechanic has one Car through an Owner. The mechanic accesses the car through the owner they service.

// Mechanic.php
public function car(): HasOneThrough
{
    return $this->hasOneThrough(Car::class, Owner::class);
}

Polymorphic relationships — one model, many parent types

Rule: A single model can belong to more than one other model type using a shared *_id and *_type column pair.

Example: A Comment can belong to either a Post or a Video.

Models:

// Comment.php
public function commentable(): MorphTo
{
    return $this->morphTo();
}

// Post.php
public function comments(): MorphMany
{
    return $this->morphMany(Comment::class, 'commentable');
}

// Video.php
public function comments(): MorphMany
{
    return $this->morphMany(Comment::class, 'commentable');
}

Migration: The comments table needs commentable_id and commentable_type.

php$table->morphs('commentable');

Step-by-Step: Generate Eloquent Relationships with AI

Step 1: Describe the relationship in one plain English sentence

The more specific, the better. Examples:

Step 2: Paste the description into LaraCopilot

LaraCopilot reads the plain English description and identifies the correct relationship type, the foreign key placement, and the migration column requirements.

Step 3: Review the generated model methods

LaraCopilot generates both sides of every relationship. Review that:

Step 4: Review the generated migration

For hasOne and hasMany, confirm the foreign key column is on the child table.

For belongsToMany, confirm a pivot table migration was generated with both foreign keys.

For polymorphic relationships, confirm $table->morphs('commentable') is present.

Step 5: Run migrations and test

php artisan migrate
php artisan test --filter RelationshipTest

6 Relationship Mistakes That Create Silent Query Bugs

Mistake 1: Putting belongsTo on the wrong model.

The foreign key is on the comments table, so belongsTo goes in the Comment model, not the Post model.

Do this instead: Find the table with the _id column. That model gets belongsTo.

Mistake 2: Naming a pivot table incorrectly.

Laravel expects alphabetical order: post_tag, not tag_post.

Do this instead: Alphabetize both model names, singular, with an underscore.

Mistake 3: Forgetting ->constrained() on foreign key migrations.

Without it, the foreign key reference exists without a database constraint, which allows orphaned records.

Do this instead: Always chain ->constrained() after foreignId().

Mistake 4: Using hasManyThrough for a many-to-many relationship.

hasManyThrough chains two one-to-many relationships. It does not replace a pivot table.

Do this instead: If both models can have many of the other, use belongsToMany.

Mistake 5: Skipping ->withTimestamps() on pivot tables that need created_at.

The pivot table will not populate timestamp columns without this method call.

Do this instead: Add ->withTimestamps() to belongsToMany() when the pivot table has timestamp columns.

Mistake 6: Defining only one side of the relationship.

Defining hasMany on Post without belongsTo on Comment means you cannot traverse the relationship in both directions.

Do this instead: Always define both sides — the parent and the inverse.

Eloquent Relationship Assumptions That Break Queries

Assumption 1: belongsTo and hasMany are interchangeable depending on where you call them.

They are inverses of each other, but the method name tells Eloquent where to look for the foreign key. Swapping them without changing the schema produces silent null returns, not exceptions.

Assumption 2: A pivot table always needs its own Model class.

A basic pivot table does not require a model. You only need a dedicated Pivot model when querying the pivot table directly or attaching custom behavior to it.

Assumption 3: Polymorphic relationships are for edge cases only.

Polymorphic relationships are the correct tool any time one model can belong to more than one parent type. Comments, tags, images, and notifications are everyday real-world uses.

Assumption 4: AI cannot generate correct Eloquent relationships without knowing the full schema.

A Laravel-native AI generator with schema context generates both model methods and migrations correctly, including the correct foreign key placement and pivot table naming conventions, from a plain English description.

Why Getting the Relationship Direction Wrong Costs More Than the Debug Time

The Laravel documentation on relationships is thorough. The confusion is not from lack of documentation, it is from the fact that hasMany and belongsTo both reference the same database column from different model perspectives.

A junior developer who places belongsTo on the parent model instead of the child creates a query that returns null silently or throws a SQL error only when the relationship is actually used. The bug is not always immediately obvious, and fixing it after the migration is run means generating a new migration, running rollback, and rebuilding the data.

In a real project with eight to twelve models and two to three relationships each, getting two or three foreign keys in the wrong place is not unusual. Each one costs 20 to 60 minutes of debugging. Generating correct relationships from the start avoids that debugging entirely.

FLIP Framework: Foreign Key Logic Identification Pattern

The FLIP Framework is a four-step mental model for identifying the correct Eloquent relationship and foreign key placement without referencing documentation.

F — Find the “many” side. Which model has more rows? That model’s table usually holds the foreign key. A comments table has more rows than a posts table, so comments holds post_id.

L — Look for a pivot. If both models can have many of the other, neither holds the foreign key directly. A pivot table is needed. Use belongsToMany.

I — Inverse is always belongsTo. The model with the foreign key column always calls belongsTo. No exceptions.

P — Parent defines hasOne or hasMany. The model without the foreign key column defines hasOne (one child expected) or hasMany (multiple children expected).

When to use it: Any time you are setting up a new relationship and second-guessing the method and foreign key placement.

Pattern Most Relationship Guides Ignore

Most Eloquent tutorials focus on what each relationship type does. Almost none of them address the pattern that causes the most real-world bugs: the relationship direction and the foreign key placement are two separate decisions, and getting either one wrong does not always produce an immediate error.

hasMany on the wrong model returns an empty collection. A belongsTo on the wrong model returns null. Neither throws a PHP exception. Both create silent data bugs that surface mid-feature when the developer cannot understand why the query returns nothing.

The developers who stop hitting this problem are not the ones who memorize the documentation. They are the ones who generate both model methods and migrations together from a plain English schema description — so the direction is always correct and the silent bug never exists in the first place.

Eloquent Relationship Quick Reference

Choosing the right relationship:

Foreign key placement rules:

Migration naming rules:

Manual Relationship Setup vs AI-Generated from Plain English

Manual SetupAI-Generated with LaraCopilot
Look up relationship type in documentationDescribe the relationship in one sentence
Decide foreign key placement manuallyForeign key inferred and placed correctly
Write model method on parent onlyBoth model methods generated together
Write a separate migration manuallyMigration generated as connected output
Debug silent null returns from wrong directionCorrect direction on first generation
Repeat for every model pairAll model relationships generated in one session

Wrap-up!

Laravel Eloquent provides six primary relationship types, each with a specific foreign key rule and migration requirement. The most common source of error is placement direction: belongsTo always goes on the model that owns the foreign key column, hasMany goes on the parent, and belongsToMany requires a pivot table. A Laravel-native AI generator produces both model methods and the correct migration together from a plain English description, so the silent direction errors that are hardest to debug never reach the codebase in the first place.

Get Your Relationships Right the First Time

Describe your data model in plain English and get every model method and migration generated correctly from the start.

Try LaraCopilot Free