Skip to main content

SF-0030 · Concept · Medium

What are many to many relationships?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026

A many-to-many relationship in Salesforce lets each record on one object relate to many records on another object, and each of those to many back. Salesforce has no built-in “many-to-many” field type — you implement it with a junction object: a custom object that sits between the two related objects and holds two master-detail relationships, one to each side. Each row of the junction represents a single connection between one record from each side.

The shape of a many-to-many in Salesforce

   Student                 Student_Class__c               Class
   ┌───────┐               ┌───────────────┐              ┌────────┐
   │  Id   │◄──── M-D ─────┤ Student__c    │              │   Id   │
   │  Name │               │ Class__c      ├──── M-D ────►│  Name  │
   └───────┘               │ Grade__c      │              └────────┘
                           │ Enrolled_Date │
                           └───────────────┘
   one student → many junction rows → many classes
   one class   → many junction rows → many students

A student can take many classes; a class has many students. Each enrollment is one row in Student_Class__c holding the linkage plus any per-enrollment data (grade, enrolled date, status).

Why the junction object usually carries extra data

The junction is not just plumbing — it’s the right place to store anything that depends on the combination of both sides:

  • Student × Class → Grade, Enrolled Date, Active flag
  • Project × Resource → Hours Allocated, Role on Project
  • Account × Product → Subscription Status, Discount %
  • Opportunity × Contact → Role (Decision Maker, Influencer, etc.) — this is what OpportunityContactRole does as a standard junction

If you find yourself storing data on one side that “really belongs to the pair,” that’s a sign the junction should hold it instead.

Primary master vs secondary master

When you create the junction with two master-detail fields, the first master-detail you create becomes the primary master. This matters because:

  • The primary master controls the junction’s ownership and sharing.
  • The junction record appears in the related list of the primary master with default placement.
  • Cascade delete behavior: deleting the primary master deletes all junction records pointing to it (and that does not delete the secondary master’s records — only the junction rows).
  • Roll-up summary fields can be created on either master, summarizing junction-level data.

You can change which master is primary later by manipulating the field order, but it has sharing implications — design this carefully up front.

Standard many-to-many relationships in Salesforce

Salesforce ships with several built-in junction-style relationships:

JunctionSidesPer-junction data
OpportunityContactRoleOpportunity ↔ ContactRole, Is Primary
CampaignMemberCampaign ↔ Lead/ContactStatus, Has Responded
AccountContactRelationAccount ↔ Contact (Contacts to Multiple Accounts feature)Role, Direct
CaseTeamMemberCase ↔ UserTeam Role, Access
OpportunityTeamMemberOpportunity ↔ UserTeam Role, Access
UserRecordAccess(system table for sharing)

These are not implemented as user-defined junction objects exactly the same way — some use master-details, some use lookup pairs with system fields. But conceptually they all serve the many-to-many purpose.

Junction limits and considerations

ConcernDetail
Master-detail per junctionExactly 2 (one to each side) — the standard junction pattern
Required-nessBoth master-details are required (it’s master-detail, after all)
ReparentingAllowed if “Allow reparenting” is checked on the secondary master field
SharingControlled by the primary master’s OWD and sharing
Cascade deleteJunction rows deleted when either master is deleted
UndeleteIf the primary master is undeleted from the Recycle Bin within 15 days, its junction rows come back; if a non-primary master is undeleted, junction rows do not automatically come back
Roll-up summaryAvailable on either master

Real scenario

“You’re modeling Projects and Skills. A project requires multiple skills; a skill can be needed by many projects. You also need to track required proficiency level for each (skill, project) combination.”

Build:

  1. Project__c (custom object — or use a standard if it fits).
  2. Skill__c (custom object).
  3. Project_Skill__c (junction custom object).
    • Master-Detail 1: Project__c (creates the primary master).
    • Master-Detail 2: Skill__c.
    • Custom fields: Required_Proficiency__c (Picklist: Junior/Mid/Senior), Hours_Estimate__c (Number).
  4. On Project, optional roll-up: Skill_Count__c = COUNT(Project_Skill__c).

Now each project has many skills; each skill is referenced by many projects; the proficiency level is captured exactly once per pair.

Lookup-based many-to-many — when and why

Strictly, you can build a many-to-many with two lookup fields on the junction instead of master-detail. Salesforce sometimes calls this a “weakly-linked junction.” Reasons to consider:

  • Junction records should keep their own ownership and sharing (e.g., a “Recommendation” between two records that the recommender owns).
  • You want junction records to not cascade-delete with either side.
  • Either side could be missing (e.g., during data migration).

Trade-off: no roll-up summaries are possible on either master, you lose cascade delete, and sharing must be designed independently for the junction.

The master-detail pattern is the strong default; lookup-based junctions exist for the specific cases above.

Verified against: Salesforce Help — Many-to-Many Relationships and Object Relationships Overview. Last reviewed 2026-05-17.