How to Build a Simple CRM in Google Sheets (2026)
HomeGoogle SheetsBuild a Simple CRM
Google SheetsHow-To Post⏱ 11 min read

How to Build a Simple CRM in Google Sheets

You don't need a $99/user/month tool to track contacts, deals, and follow-ups. A well-structured Google Sheet handles the CRM basics for small teams — with the bonus that you control every field, formula, and view. This guide walks through building one from scratch: contacts, deals, activity log, and a summary dashboard.

The 4-sheet structure

A working CRM needs four tabs:

  1. Contacts — everyone you know or want to know
  2. Deals — open opportunities and their stage
  3. Activities — call, email, meeting, and note log
  4. Dashboard — pipeline value, activity counts, key metrics

Each row in Deals references a Contact ID. Each row in Activities references a Contact and optionally a Deal. This linking is what turns a flat spreadsheet into a CRM.

Start with the template, then customize

Build the structure below first with your own fields. Once you've used it for a few weeks, you'll know exactly which columns you need to add, remove, or split. Skip the temptation to plan every field upfront — real usage teaches faster than planning.

Sheet 1 — Contacts

Row 1 headers. Row 2 onwards, one row per person.

Contacts
A: Contact IDB: NameC: CompanyD: EmailE: PhoneF: SourceG: OwnerH: StatusI: Last TouchedJ: Notes
2C001Ayesha MalikNorthgate Ltdayesha@…+92…ReferralZainWarm2026-09-01Wants Q4 pricing
3C002David ChenKestrel Codavid@…+1…WebsiteZainCold2026-07-15Old lead, revisit
4C003Priya NairSolsticepriya@…+91…LinkedInSaraHot2026-09-05Demo scheduled

Column notes

  • Contact ID — a stable unique identifier. Manual (C001, C002...) or formula-based. Deals and Activities reference this.
  • Source — where the lead came from. Great for later analysis ("what channel converts best?").
  • Owner — who's responsible for the relationship. Filter by owner for individual pipelines.
  • Status — Cold, Warm, Hot, Customer, Lost. Drives the "who needs attention" logic.
  • Last Touched — updated manually or from the Activities sheet (see formulas below).

Sheet 2 — Deals

Deals
A: Deal IDB: Deal NameC: Contact IDD: Contact NameE: StageF: ValueG: ProbabilityH: WeightedI: Expected CloseJ: Owner
2D001Northgate Q4 pilotC001Ayesha MalikProposal15,00050%7,5002026-10-15Zain
3D002Solstice retainerC003Priya NairDemo36,00030%10,8002026-11-30Sara

Pull the Contact Name automatically

Column D (Contact Name) should look itself up rather than being typed. That way if you rename a contact, it updates everywhere.

=IFERROR(VLOOKUP(C2, Contacts!A:B, 2, FALSE), "")

Or with the newer XLOOKUP:

=IFERROR(XLOOKUP(C2, Contacts!A:A, Contacts!B:B), "")

Auto-calculate the weighted value

Weighted value = deal value × probability. Column H:

=F2 * G2

If your Probability column stores percentages as text ("50%"), use =F2 * (G2/1) or store as a decimal (0.5) with % display formatting.

Sheet 3 — Activities

Activities
A: DateB: Contact IDC: Deal IDD: TypeE: DirectionF: NoteG: Owner
22026-09-05C003D002MeetingDemo went well, sending proposal MondaySara
32026-09-01C001D001EmailOutSent Q4 pricingZain
42026-08-28C001CallInAsked about training bundlesZain

Column notes

  • Date — press Ctrl+; to insert today's date as a static value
  • Contact ID / Deal ID — reference the entity this activity relates to. Deal ID is optional (some activities aren't tied to a specific deal).
  • Type — Call, Email, Meeting, Note, Task
  • Direction — In / Out (mostly for calls and emails)

Update "Last Touched" on the Contact automatically

Instead of manually updating column I on the Contacts sheet, compute it from the Activities log. In Contacts column I:

=IFERROR(MAXIFS(Activities!A:A, Activities!B:B, A2), "")

Returns the most recent Activity date for each Contact ID. Format the cell as a date.

Data validation dropdowns

Manual entry of Stage, Source, Type, and Status leads to typos ("Hot", "hot", "HOT", "H0t") that break your filters. Fix with dropdowns.

  1. Select the column (e.g., Deals!E2:E)
  2. Data → Data validation → Add rule
  3. Criteria: Dropdown — enter each option on its own line: New, Contacted, Qualified, Demo, Proposal, Negotiation, Won, Lost
  4. Save

Now every cell in that column shows a clickable dropdown. No typos. Filters and pivot tables work cleanly.

Store dropdown values on a Settings sheet

For dropdowns you'll edit over time (Sources, Owners, Stages), put the options in a "Settings" sheet with one option per row. In Data validation, use "Dropdown (from a range)" pointing to that column. Now you add or rename options in one place.

Pipeline formulas

Total pipeline value

=SUM(Deals!F:F)

Total weighted pipeline (probability-adjusted)

=SUM(Deals!H:H)

Pipeline value in the "Won" column only

=SUMIF(Deals!E:E, "Won", Deals!F:F)

Deals expected to close this month

=SUMIFS(Deals!F:F, Deals!I:I, ">="&EOMONTH(TODAY(),-1)+1, Deals!I:I, "<="&EOMONTH(TODAY(),0))

Days since last touched (per contact)

Add a "Days Since" column to Contacts (column K):

=IF(I2="", "", TODAY() - I2)

Next action for each contact

Use IFS to derive a recommended action based on status and days since:

=IFS( H2="Hot", "Close this week", H2="Warm", "Follow up this week", K2 > 90, "Reactivate — 3+ months cold", K2 > 30, "Check in", TRUE, "No action")

Count of activities per contact

=COUNTIF(Activities!B:B, A2)

Conditional formatting

Highlight what needs attention. Format → Conditional formatting.

Cold contacts turn orange

On the Contacts sheet, select rows A2:K:

  • Custom formula: =$K2 > 60
  • Fill: light orange

Any row where "Days Since" (column K) exceeds 60 gets highlighted.

Very cold (90+ days) turn red

  • Custom formula: =$K2 > 90
  • Fill: light red

Order matters — put the "> 90" rule above "> 60" in the rules list.

Hot deals in the pipeline

On Deals, select A2:J:

  • Custom formula: =$G2 >= 0.7
  • Fill: light green

Stalled deals (close date past)

  • Custom formula: =AND($I2 < TODAY(), $E2 <> "Won", $E2 <> "Lost")
  • Fill: light red

Highlights any deal whose expected close is behind schedule and isn't yet closed.

Sheet 4 — Dashboard

The at-a-glance view. Keep it simple: 6-8 metrics and 2-3 charts.

Top-row summary cells

MetricFormula
Total contacts=COUNTA(Contacts!A2:A)
Hot contacts=COUNTIF(Contacts!H:H, "Hot")
Open deals=COUNTIFS(Deals!E:E, "<>Won", Deals!E:E, "<>Lost")
Pipeline value=SUMIFS(Deals!F:F, Deals!E:E, "<>Won", Deals!E:E, "<>Lost")
Weighted pipeline=SUMIFS(Deals!H:H, Deals!E:E, "<>Won", Deals!E:E, "<>Lost")
Won this month=SUMIFS(Deals!F:F, Deals!E:E, "Won", Deals!I:I, ">="&EOMONTH(TODAY(),-1)+1)
Activities this week=COUNTIFS(Activities!A:A, ">="&TODAY()-WEEKDAY(TODAY(),2)+1)

Deals by stage — a QUERY-based summary

Use QUERY to build a live count and total per stage:

=QUERY(Deals!A:J, "SELECT E, COUNT(A), SUM(F) WHERE E IS NOT NULL AND E <> 'Won' AND E <> 'Lost' GROUP BY E LABEL COUNT(A) 'Deals', SUM(F) 'Value'", 1)

Returns a small table: Stage | Deals | Value. Drop a bar chart on top to visualize.

Add a stage chart

  1. Select the QUERY output range
  2. Insert → Chart
  3. Chart type: Column chart
  4. Stage on X-axis, Value on Y-axis
  5. Customize colors to match your brand

Add a source-of-lead pie chart

=QUERY(Contacts!A:J, "SELECT F, COUNT(A) WHERE F IS NOT NULL GROUP BY F LABEL COUNT(A) 'Contacts'", 1)

Chart the result as a pie or donut.

Optional automations

Send daily reminder emails

Extensions → Apps Script. Paste a small script that runs on a daily trigger:

function dailyReminders() {
  const sheet = SpreadsheetApp.getActive().getSheetByName('Contacts');
  const data = sheet.getDataRange().getValues();
  const rows = data.slice(1)
    .filter(r => {
      const daysSince = r[10]; // column K
      return typeof daysSince === 'number' && daysSince > 30;
    })
    .map(r => `${r[1]} (${r[2]}) — ${r[10]} days since touched`);

  if (rows.length) {
    MailApp.sendEmail({
      to: Session.getActiveUser().getEmail(),
      subject: `CRM: ${rows.length} contacts need attention`,
      body: rows.join('\n')
    });
  }
}

Then: Apps Script → Triggers → Add Trigger → dailyReminders → Time-driven → Day timer → 8am.

Test the trigger first

Run dailyReminders() manually from the Apps Script editor before setting the trigger. Google will ask for permission to read the sheet and send email. Approve once; the trigger runs unattended after that.

Auto-log emails as activities

A Zapier or Make integration can watch your Gmail inbox and drop new emails to specific senders into the Activities sheet. Free-tier plans on both services handle a small CRM's volume.

Slack notifications on stage change

Apps Script onEdit(e) trigger: watch the Deals sheet, and when a stage changes to "Won", post to a Slack webhook. Under 20 lines of code once you have the webhook URL.

Where Sheets stops being enough

A Sheets CRM is excellent up to a point. Signs you're outgrowing it:

  • Over 5,000 contacts and formulas are getting slow
  • You need multiple people editing simultaneously without stepping on each other
  • Deal automation is getting complex — multi-step workflows, approval routing
  • You need email tracking (opens, clicks) built into the CRM
  • Reports need custom user permissions (some people can only see their pipeline)

At that point, move to a dedicated tool. But by then, you'll know your process well enough to pick the right one — because you built the workflow in Sheets first.

Common pitfalls

Duplicate contacts

Without discipline, the same person ends up as C001 and C047 with different spellings. Add a duplicate check: on Contacts, a helper column =COUNTIF(D:D, D2) counts occurrences of each email. Conditional-format rows where the count is > 1.

Broken lookups when someone deletes a row

Deleting a Contact row breaks any Deal or Activity referencing that ID. Instead of deleting, set Status to "Deleted" and filter it out of views. Preserves history and prevents broken references.

Sensitive data in a shared sheet

Anyone with edit access sees everything. Use Google Workspace permissions carefully: view-only for stakeholders, edit for salespeople, and don't share the sheet publicly. Consider a separate confidential sheet for high-value fields (deal margin, personal notes).

Freeze the header row

View → Freeze → 1 row. Keeps column headers visible as you scroll. Small change, big improvement for daily use.

Sheets Wizard

CRM formulas, without the syntax struggle

XLOOKUP references, SUMIFS across sheets, and QUERY strings for dashboards are the CRM's plumbing — and where most Sheets projects stall. Sheets Wizard takes plain-English intent ("show me all hot deals closing this month per owner") and writes the QUERY or SUMIFS with correct references.

Install Sheets Wizard →

Frequently asked questions

Can Google Sheets really work as a CRM?

Yes — for teams up to 10 people and a few thousand contacts. Beyond that, dedicated tools (HubSpot, Pipedrive, Attio) scale better. Sheets wins when your process is unique and you want full control.

How many sheets should the CRM have?

Four is the sweet spot: Contacts, Deals, Activities, Dashboard. Add Companies, Products, or Settings tabs as needs grow.

Should each deal link to a contact?

Yes — via a shared Contact ID. Use VLOOKUP or XLOOKUP to pull contact details into Deals. This normalized structure keeps you from duplicating contact info across deals.

How do I automate follow-up reminders?

Simple: conditional formatting to highlight overdue rows. Advanced: Apps Script to email a daily reminder list. Both work; conditional formatting is enough for most teams.