Watch our latest video available on Youtube.
Tutorials/Tutorial

How to Use Junction Tables in Airtable for Many-to-Many Relationships

A junction table is the pattern Airtable doesn't tell you about until you need it. The moment your many-to-many relationship needs to store data about itself — a grade, a quantity, a date, a role — you need a third table. This guide is the complete walkthrough with four real business examples, the rollup patterns that pay for the extra table, and the mistakes that turn a clean junction into a tangle.

Advanced16 min readJul 13, 2026

The phrase "Airtable junction table" appears in roughly zero of Airtable's onboarding materials. It also appears in roughly every well-designed Airtable base running a real business. The reason for the gap: junction tables are the most important pattern in relational data modelling, and Airtable's visual approach hides them just enough that most builders go years without learning the name.

This guide explains what a junction table is, when you need one, and how to build it cleanly. We will work through four real business examples — student enrollments, order line items, event attendance, and project assignments — with the exact field configurations, primary-field formulas, and rollup patterns. If you have not yet mastered the building blocks, linked records, lookups, and rollups are prerequisites — this article assumes you have them.

What This Guide Covers

  • What a junction table is and when you need one
  • Direct many-to-many vs junction table — the test
  • Building a junction step-by-step
  • Example 1: Student enrollments (grades, dates)
  • Example 2: Order line items (quantity, price)
  • Example 3: Event attendance (status, check-in)
  • Example 4: Project assignments (role, allocation)
  • Rollup patterns that pay for the extra table
  • Common mistakes and how to migrate from direct links
  • When a junction table is the wrong answer

What a Junction Table Actually Is

A junction table is a third table that sits between two other tables and stores the relationship as a record. Each row on the junction has at least two linked-record fields — one pointing at each parent — and additional fields that describe the connection.

In classical database terms, this is called an associative entity or a bridge table. In Airtable, the most common names are:

  • Enrollments (Students × Classes)
  • Order Items / Order Lines (Orders × Products)
  • Registrations / Attendance (Events × Attendees)
  • Assignments (Projects × Team Members)
  • Stock Movements (Products × Warehouses)
  • Memberships (People × Groups)

The pattern is always the same. The relationship gets its own table because the relationship has its own data.

Direct Many-to-Many vs Junction Table: The Test

Airtable supports direct many-to-many links — just add a linked-record field on each side and let both fields hold multiple records. For simple "is-a-member-of" relationships, that is enough.

You need a junction table when the relationship has its own attributes. Walk this checklist:

QuestionDirect link works?Junction needed?
Just need to know which records are connected?YesNo
Need a quantity on each connection (3 widgets per order)?NoYes
Need a date specific to the connection (enrollment date)?NoYes
Need a role or status on the connection (lead, member)?NoYes
Need to score or rank the connection (grade, rating)?NoYes
Need to record connections changing over time?NoYes

If any answer in the right column is Yes, you need a junction table. The signal is always: "I want to store a field, but there is no record to put it on." That missing record is the junction.

Direct many-to-many links record that a relationship exists. Junction tables record that a relationship exists and what it is.

Building a Junction Table — The Universal Recipe

The exact same five steps work for every junction table you will ever build.

  1. Create a new table named after the relationship (Enrollments, Order Items, Assignments). Name it as a noun describing what one row represents — one enrollment, one order item, one assignment.
  2. Add a linked-record field pointing to the first parent table. Restrict it to a single record per cell.
  3. Add a linked-record field pointing to the second parent table. Restrict it to a single record per cell.
  4. Add the relationship's own fields — Quantity, Role, Date, Grade, Status, Notes, anything that describes this specific connection.
  5. Set the primary field to a formula that concatenates identifying fields from both linked records via lookups. This makes the junction rows readable when they show up on parent records.

That is the whole recipe. The remaining work is choosing useful rollups on each parent to surface the junction data back where users want to see it.

Example 1: Student Enrollments

The canonical many-to-many. Students take many Classes. Classes have many Students. Each enrollment has its own grade, enrollment date, and attendance rate — none of which belong on either Student or Class.

Tables

  • Students — Name, Email, Year, Major
  • Classes — Class Name, Subject, Term, Credits
  • Enrollments (junction) — links to Student, Class, plus enrollment data

Enrollments table fields

FieldTypeConfiguration
IdentifierFormula (primary){Student Name} & " — " & {Class Name}
StudentLinked record→ Students, single record
ClassLinked record→ Classes, single record
Student NameLookupStudent → Name
Class NameLookupClass → Class Name
Enrollment DateDate
StatusSingle selectEnrolled, Completed, Dropped, Failed
Final GradeNumber0–100
Letter GradeFormulaBucketed from Final Grade (see IF statements)
Credits EarnedFormulaIF({Status}="Completed", {Class Credits}, 0)

Rollups on Students

RollupSource
Total CreditsEnrollments → Credits Earned, SUM(values)
GPAEnrollments → Final Grade, condition Status = Completed, AVERAGE(values)
Active ClassesEnrollments → Class Name, condition Status = Enrolled, COUNT

Rollups on Classes

RollupSource
Class SizeEnrollments → Student Name, condition Status = Enrolled, COUNT
Class AverageEnrollments → Final Grade, condition Status = Completed, AVERAGE
Pass RateTwo rollups (Passed count / Completed count) feeding a formula

The Enrollments table now answers questions that a direct Students ↔ Classes link could not even hold: "What was Maya's grade in CS 101?" "What is the average grade in CS 101 this term?" "Which classes did Maya drop?"

Example 2: Order Line Items

E-commerce, B2B sales, agency services — all the same pattern. Orders have many Products. Products are in many Orders. Each line item has quantity, unit price, and a per-line total.

Tables

  • Orders — Order Number, Customer, Order Date, Status
  • Products — SKU, Product Name, List Price, Stock
  • Order Items (junction)

Order Items fields

FieldTypeConfiguration
Line IDFormula (primary){Order Number} & " / " & {Product SKU}
OrderLinked record→ Orders, single
ProductLinked record→ Products, single
Order NumberLookupOrder → Order Number
Product SKULookupProduct → SKU
List PriceLookupProduct → List Price
QuantityNumber
Discount %Number0–100
Line TotalFormula{Quantity} * {List Price} * (1 - {Discount %}/100)

Rollups on Orders

RollupSource
SubtotalOrder Items → Line Total, SUM(values)
Item CountOrder Items → Quantity, SUM(values)
Product ListOrder Items → Product SKU, ARRAYJOIN(values, ", ")

Rollups on Products

RollupSource
Units SoldOrder Items → Quantity, condition Order Status = Shipped, SUM
RevenueOrder Items → Line Total, condition Order Status = Shipped, SUM
Open OrdersOrder Items → Order Number, condition Order Status = Pending, COUNTA

A direct Orders ↔ Products link has nowhere to put Quantity. The whole point of an order is to record how many of each product the customer is buying — junction table is mandatory.

Example 3: Event Attendance

Events have many Attendees. Attendees can attend many Events. Each registration has a status, ticket type, dietary needs, and a check-in time.

Tables

  • Events — Event Name, Date, Venue, Capacity
  • Attendees — Name, Email, Company, Title
  • Registrations (junction)

Registrations fields

FieldTypeConfiguration
Reg IDFormula (primary){Attendee Name} & " @ " & {Event Name}
EventLinked record→ Events, single
AttendeeLinked record→ Attendees, single
Attendee NameLookupAttendee → Name
Event NameLookupEvent → Event Name
Ticket TypeSingle selectGeneral, VIP, Speaker, Sponsor
StatusSingle selectRegistered, Confirmed, Cancelled, Attended
Registered AtCreated time
Checked In AtDate with time
Dietary NotesLong text

Rollups on Events

RollupSource
Total RegisteredRegistrations → Status, condition Status != Cancelled, COUNTA
Total AttendedRegistrations → Checked In At, COUNTA
VIP CountRegistrations → Reg ID, condition Ticket Type = VIP, COUNTA
Diet SummaryRegistrations → Dietary Notes, ARRAYJOIN(ARRAYCOMPACT(values), "; ")

The dietary notes example is where junction tables shine: the data attaches to the (attendee, event) pair, not to either side individually. Maya might be vegetarian at one event and have no notes at another.

Example 4: Project Assignments

Projects have many Team Members. Team Members work on many Projects. Each assignment has a role, an allocation percentage, and start/end dates.

Tables

  • Projects — Project Name, Client, Start, End, Status
  • Team Members — Name, Email, Department, Capacity
  • Assignments (junction)

Assignments fields

FieldTypeConfiguration
AssignmentFormula (primary){Member Name} & " on " & {Project Name}
ProjectLinked record→ Projects, single
Team MemberLinked record→ Team Members, single
Project NameLookupProject → Project Name
Member NameLookupTeam Member → Name
RoleSingle selectLead, Designer, Developer, PM, Reviewer
Allocation %Number0–100
Start DateDate
End DateDate
Hourly RateNumberOptional override; else use the member's default

Rollups on Projects

RollupSource
Team SizeAssignments → Member Name, COUNTA
Roles CoveredAssignments → Role, ARRAYUNIQUE(values)
Total AllocationAssignments → Allocation %, SUM(values)

Rollups on Team Members

RollupSource
Current ProjectsAssignments → Project Name, condition Project Status = Active
Total AllocationAssignments → Allocation %, condition Project Status = Active, SUM
Over-allocatedFormula on Team Member: IF({Total Allocation} > 100, "OVER", "OK")

The "Over-allocated" flag is a single formula on Team Member that catches anyone scheduled at over 100% across active projects — a metric impossible to compute without the Assignments junction.

The Primary Field Formula Pattern

Every junction record needs a meaningful primary field. The default is the autonumber or first-created field, which produces useless cells like "Item 1, Item 2, Item 3" when junctions show up on parent records.

The fix is a formula primary that concatenates lookups from both sides:

{Member Name} & " on " & {Project Name}

Or for orders:

{Order Number} & " / " & {Product Name} & " (" & {Quantity} & ")"

Now when you open a Project, the Assignments cell shows "Maya on Acme Rebrand" — not "rec1xJ4...". Users can read junction cells without clicking through.

For more on building good formula primary fields, see the formulas cheat sheet and the text formulas guide.

Migrating from a Direct Many-to-Many

Many bases start with a direct Students ↔ Classes link, then realise they need grades. To migrate without losing data:

  1. Create the Enrollments table with the two linked fields (Student, Class) and any new fields (Grade, Status).
  2. Write an Airtable script that iterates every Student record, reads its linked Classes, and creates one Enrollment record per (student, class) pair.
  3. Verify counts — the number of Enrollments should equal the total number of student-class pairs in the old direct link.
  4. Update views and interfaces to point at Enrollments instead of the direct link.
  5. Delete the direct linked-record field on Students and Classes (the reverse link auto-removes).

Run the migration in a sandbox base first. If you have not scripted in Airtable before, the same migration can be done with Make using an iterator over the source linked field — slower, but no code.

Common Mistakes

Treating the junction as an afterthought. New builders create a "Notes" table to capture the grade and link both Students and Classes to it as multi-record. This is not a junction — it is a parallel many-to-many with no rules. Always restrict junction linked fields to single record so each row represents exactly one connection.

Setting up junctions when you do not need them. If the relationship has no attributes of its own, a direct many-to-many is simpler and faster. Tags on Articles, Categories on Products, Skills on People — none need junctions unless you start tracking per-pair data.

Forgetting the primary field formula. Junctions with default primary fields show up as gibberish on parent records, making the system unusable for end users. Set the formula primary first.

Not adding lookups for the parent fields. Without lookups for Student Name and Class Name on the junction, every view of Enrollments shows only linked-record IDs. Add the lookups immediately on table creation.

Building rollups on the wrong side. Total credits goes on Student, not on Class. Class size goes on Class, not on Student. The aggregation lives on the parent whose perspective the metric describes.

Stacking three or more linked records when a junction with three linked fields would be cleaner. When a relationship genuinely involves three entities (a stock movement involves Product + Warehouse + Supplier), one junction with three linked-record fields is cleaner than three pairwise junctions.

When a Junction Table Is the Wrong Answer

Two cases where a junction is overkill.

Pure tagging or categorisation. Many-to-many with no relationship attributes. A direct linked-record field on both sides is the right tool. Tags, categories, channels, regions.

Hierarchical containment with no per-pair data. If a Project contains many Tasks and a Task belongs to exactly one Project, that is one-to-many — a single linked-record field on Task, no junction needed.

The signal that a junction is wrong is the absence of any field that "belongs to the pair." If there are no per-pair fields, you do not have a junction's worth of data.

Where Junction Tables Fit

Junction tables are how Airtable handles the parts of relational modelling that one-to-many cannot reach. Combined with linked records, lookups for surfacing parent fields on the junction, and rollups for aggregating junction data back to the parents, you can model any business relationship Airtable can hold.

Most real bases have at least one junction table. The CRM has Activities between Contacts and Deals. The agency has Assignments between Projects and Team. The e-commerce base has Order Items between Orders and Products. Once you have built one cleanly, the rest follow the same five-step recipe.

For the official Airtable take, the linked record field documentation covers the underlying mechanics that junction tables build on. From there, the practical work is choosing good rollups and writing primary-field formulas that make the junction rows readable to humans.

Frequently Asked Questions

Common questions about this tutorial.

Ready to Transform Your Business Operations?

Join 100+ companies that have automated their way to success. Get started today and see the difference.