Super Advanced · Lesson 20 of 22

Building a Full CRM in Coda

A CRM is the perfect Coda capstone: five interconnected tables, relation columns, rollup formulas, a pipeline board, automations, and AI columns — all woven into a system your whole team will actually use.

⏱ ~45 min 🏗️ Capstone Project ⚡ Lessons 3–12 required
Architecture Companies Contacts Deals Pipeline Activities Dashboard Automations AI Integration Practice
01 — Architecture

Five tables, one connected system.

A proper CRM isn't a single contacts list — it's a graph of related objects. In Coda, each object type becomes a table, and relation columns connect them. Before building any table, sketch the data model so every connection is intentional.

🏢

Companies + Contacts

Companies are accounts. Contacts belong to companies via a relation column. One company can have many contacts.

💼

Deals

Each deal links to a company. The deal moves through pipeline stages and tracks value, probability, and close date.

📋

Activities + Notes

Every call, email, or meeting is an Activity row linked to a contact and optionally a deal. Notes live as a Canvas column on Contacts.

CRM Data Model — Relation Column Map
🏢
Companies
Name · Industry · ARR · Owner · Stage · Website
Root table
relation ↓
👤
Contacts
Name · Company → · Title · Email · Last Activity · Canvas Notes
💼
Deals
Name · Company → · Value · Stage · Close Date · Probability
📋
Activities
Title · Type · Contact → · Deal → · Date · Notes · Owner
Build in order Create Companies first, then Contacts (which references Companies), then Deals (references Companies), then Activities (references Contacts and Deals). Relation columns can only point to tables that already exist.
02 — Companies Table

Accounts: the root of the graph.

Every contact, deal, and activity ultimately traces back to a company. Build the Companies table first with a clean set of core columns, then add the derived columns that roll up data from child tables.

Companies Table — Columns
Column Type Notes
Name Text Display column — shown in relation lookups
Industry Select SaaS · E-commerce · Healthcare · Finance · Other
ARR Number Format: currency. Annual Recurring Revenue.
Owner Person The account executive responsible
Stage Select Prospect Customer Churned
Website URL Opens as a link in the row detail panel
Notes Canvas Rich-text account notes, embedded docs
Contact Count Formula Contacts.Filter([Company]=thisRow).Count()
Open Pipeline Formula Deals.Filter([Company]=thisRow).Filter([Stage]!="Lost").Sum([Value])

The two formula columns — Contact Count and Open Pipeline — are derived from child tables using Filter() and Sum(). They update automatically as contacts and deals are added. Add these columns after creating the Contacts and Deals tables.

Set Name as the display column Right-click the Name column header → "Set as display column." This ensures that when Contacts and Deals reference a company, they show the company's Name — not a row ID.
03 — Contacts Table

People, linked to their company.

The Contacts table holds the individual people you work with. The most important column is the relation to Companies — it connects each person to their account and makes company attributes available as lookup columns in Contacts.

Contacts Table — Columns
Column Type Notes
Name Text Display column
Company Relation → Companies table
Title Text Job title / role
Email Text Format: Email
Phone Text Format: Phone
Last Activity Date Updated by automation when an Activity is logged
Owner Person The rep who owns this relationship
Interaction History Canvas Free-form notes, email snippets, meeting summaries
Company Industry Lookup [Company].Industry — inherited from the relation
Company Owner Lookup [Company].Owner
Practice: add a "Full Name" formula column Some CRMs store first and last name separately. If your Contacts table has First Name and Last Name columns, add a formula column: [First Name] & " " & [Last Name] and set it as the display column. This keeps lookups readable while keeping the data structured.
04 — Deals Pipeline

Revenue tracked through stages.

The Deals table is the heart of the CRM's commercial layer. It tracks every sales opportunity from first qualification through to won or lost. The Kanban board view — grouped by Stage — turns the Deals table into a visual pipeline your whole sales team can manage.

Deals Table — Columns
Column Type Notes
Name Text Deal name — display column
Company Relation → Companies
Value Number Format: currency. Expected deal size.
Stage Select Prospect Qualified Proposal Negotiation Won Lost
Close Date Date Expected close or actual close
Owner Person Sales rep responsible
Probability Number Format: percent. 0–100%.
Weighted Value Formula thisRow.[Value] * thisRow.[Probability]

Build the Pipeline Kanban view

In the Deals table, click + New viewBoard. Set group-by to Stage. Set sort to Close Date ascending. This gives you a visual pipeline where cards move between columns as deals progress. Display Value and Owner as card fields for quick scanning.

Weighted Value gives a realistic forecast A deal at "Negotiation" (80% probability) on a $100k deal shows $80k weighted. Sum the Weighted Value column and you have your expected-close revenue number — more honest than summing face value across all open deals.
05 — Activities Table

Every touchpoint, logged and linked.

The Activities table is your interaction log. Every call, email, meeting, or follow-up note becomes a row. Each activity links to a contact and optionally to a deal — so you can filter "all activities for Deal X" or "all activities for Contact Y" at a glance.

Activities Table — Columns
Column Type Notes
Title Text Display column — e.g. "Discovery call with Sarah"
Type Select Call Email Meeting Note
Contact Relation → Contacts
Deal Relation → Deals (optional)
Date Date When the activity occurred
Notes Text Summary of the interaction
Owner Person Who conducted the activity

Calendar view for scheduling

Add a Calendar view of the Activities table with the Date column as the calendar field. This gives your team a visual week-by-week schedule of all customer touchpoints. Filter by Owner to see each rep's activity calendar.

Automation: update Last Activity on the Contact

When a new Activity row is created: Trigger = row added to Activities. Action = modify a row in Contacts where Contacts = this Activity's Contact. Set Last Activity = Today(). This keeps the Contact table's Last Activity column current without manual updates.

06 — Dashboard Views

Everything your team needs, on one page.

A Coda page acts as a dashboard by embedding multiple filtered views of the five tables in one place. The home page of your CRM doc is the central command center — no separate BI tool needed.

📊

Open Pipeline Board

Embed the Deals Board view filtered to Stage != Won and Stage != Lost. Reps see their whole pipeline at a glance. Sort by Close Date to keep urgent deals visible.

📅

This Week's Activities

Embed a filtered Calendar view: Date is this week. Grouped by day. Lets the team see what's scheduled and what was logged without switching tables.

⚠️

At-Risk Deals

Embed a Deals table filtered to: Stage = "Proposal" AND Close Date < DateAdd(Today(), 14, "days"). These are the deals closing soon that still haven't moved — flag them for immediate action.

🏆

Top Companies by Pipeline

Embed the Companies table sorted descending by Open Pipeline. Instantly see which accounts have the most revenue potential in play.

Lock dashboard views After setting up the embedded views on the dashboard page, lock each view (three-dot menu → Lock layout) so they don't get accidentally re-sorted or re-filtered by someone clicking around. The underlying table data is always editable — only the view layout is locked.
07 — Automations

The CRM runs itself — mostly.

Automations are what turn a Coda CRM from a fancy spreadsheet into an active system. Each automation below handles something that reps would otherwise do manually or forget.

1 — New deal → Slack notify

Trigger: row added to Deals. Action: send Slack message to #sales. Message: "New deal: [Name] — $[Value] for [Company]. Owner: [Owner]." Keeps the whole team aware of new opportunities.

2 — Deal Won → create Project

Trigger: Deals.[Stage] changes to "Won". Action: create row in Projects table (from Lesson 21) with Name = "Implementation: " & [Deal Name], Owner = Deal Owner, Start Date = Today(). Bridges CRM to PM.

3 — No activity for 30 days

Trigger: scheduled, daily at 9am. Condition: Contact.[Last Activity] < DateAdd(Today(), -30, "days") AND Contact.[Stage] = "Active". Action: Coda notification to Contact.[Owner]. Prevent contacts from going cold.

4 — Flag overdue deals

Trigger: scheduled, daily. Condition: Deal.[Close Date] < Today() AND Deal.[Stage] is not Won or Lost. Action: Coda notification to Deal.[Owner]. Ensures deals don't silently sit past their close date.

08 — AI Integration

AI columns that read your deal data.

Now that the CRM is built, layer in AI columns. These columns read actual row data — deal stages, activity notes, contact history — and generate summaries and recommendations per row. This is where Lesson 18's AI skills pay off at scale.

AI Columns in the CRM
Deals table — "Deal Situation" AI column
Summarize the deal situation for "@[Name]" in 2 sentences.
Latest activity notes: @[Latest Activity Notes]
Current stage: @[Stage]. Value: @[Value].
Contacts table — "Next Action" AI column
Based on last activity date @[Last Activity] and
notes: @[Interaction History], suggest one specific
next action for this contact in one sentence.
Activities table — "Sentiment" AI column (Classify)
Classify the sentiment of this activity note as exactly one of:
Positive, Neutral, Concerning.
Notes: "@[Notes]". Respond with only the label.
Pause AI columns until the CRM has real data Building the system with test data will consume credits unnecessarily. Build the full structure first, enter 10–20 real rows, then turn on the AI columns to confirm quality before enabling auto-run for new rows.
Knowledge Check

Test what you've built.

Lesson 20 Quiz

5 Questions
Question 1 of 5
In what order should you build the five CRM tables?
✓ Build Companies first, then Contacts (which references Companies), then Deals, then Activities. Relation columns require the target table to exist when you create them.
Question 2 of 5
What does the "Open Pipeline" formula on the Companies table calculate?
✓ Deals.Filter([Company]=thisRow).Filter([Stage]!="Lost").Sum([Value]) sums active deal values for this company. Won deals are included because they represent revenue — only Lost deals are excluded.
Question 3 of 5
What automation keeps the "Last Activity" column on Contacts current?
✓ An automation with trigger "row added to Activities" modifies the linked Contact row and sets Last Activity = Today(). This runs in real-time whenever an activity is logged.
Question 4 of 5
What does the "At-Risk Deals" dashboard view filter for?
✓ At-risk = Stage is "Proposal" AND Close Date < DateAdd(Today(), 14, "days"). These are deals where a proposal has been sent but there's been no forward movement, with the clock ticking.
Question 5 of 5
What does the "Deal Won" automation create in another table?
✓ When a Deal's Stage changes to "Won," an automation creates a Project row: Name = "Implementation: " + Deal Name, Owner = Deal Owner, Start Date = Today(). This connects the CRM to the project management system built in Lesson 21.
← Previous Coda API: Reading & Writing with REST Lesson 19