Build a Free CRM System with Google Sheets and SheetToAPI in 2026
Tutorial

Build a Free CRM System with Google Sheets and SheetToAPI in 2026

Pikebyte.dev
March 12, 2026
11 min read
#crm#google-sheets-crm#sheettoapi#business-automation#no-code

Build a Free CRM System with Google Sheets and SheetToAPI in 2026

Salesforce costs $165/month per user. Hubspot's basic plan is $50/month. Pipedrive requires $14-99/month.

What if you could build a fully functional CRM system for free?

With Google Sheets as your database and SheetToAPI as your REST API, you can. This guide shows you how to build a complete CRM that rivals paid solutions — without the price tag.


🎯 Why CRM Software Is Overpriced

Traditional CRM problems:

IssueTypical CRMGoogle Sheets + SheetToAPI
Monthly Cost$50-200 per userFree
Setup TimeWeeks of trainingHours
CustomizationLimited to templatesUnlimited
Team AdoptionHigh learning curveThey already know Sheets
Data Lock-InHard to exportIt's already a Spreadsheet

📊 Complete CRM Architecture

Here's what we'll build:

[Sales Team's Dashboard]
    ↓
[SheetToAPI REST API]
    ↓
[Google Sheets Database]
    ├── Contacts Sheet
    ├── Deals Sheet
    ├── Activities Sheet
    └── Companies Sheet
    ↓
[Zapier Automation]
    ├── Email notifications
    ├── Calendar integrations
    └── Pipeline updates

Each sheet is a different data entity, and SheetToAPI makes them instantly queryable.


📋 Step 1: Design Your CRM Database

Create a Google Sheets file with these tabs:

1. Contacts Sheet:

| id | first_name | last_name | email            | phone        | company  | status | created_at |
|----|-----------|-----------|------------------|--------------|----------|--------|------------|
| 1  | John      | Smith     | john@acme.com    | 555-1234     | Acme Inc | lead   | 2026-03-01 |
| 2  | Jane      | Doe       | jane@techco.com  | 555-5678     | TechCo   | customer | 2026-02-15 |

Columns:

  • id - Unique contact ID
  • first_name, last_name - Contact details
  • email, phone - Contact info
  • company - Company they work for
  • status - lead, prospect, customer, inactive
  • created_at - When you added them

2. Companies Sheet:

| id | company_name      | industry | website          | employees | phone       | revenue     |
|----|------------------|----------|------------------|-----------|-------------|-------------|
| 1  | Acme Inc         | Software | acme.com         | 50        | 555-9999    | $5M         |
| 2  | TechCo Solutions | Consulting | techco.com     | 120       | 555-8888    | $10M        |

3. Deals Sheet:

| id | deal_name          | company  | amount | stage    | probability | close_date | owner  |
|----|-------------------|----------|--------|----------|------------|------------|--------|
| 1  | Enterprise License | Acme Inc | 50000  | proposal | 60%        | 2026-04-15 | John   |
| 2  | Consulting Project | TechCo   | 25000  | contract | 85%        | 2026-04-01 | Jane   |

Stages: prospect, quote, proposal, contract, won, lost

4. Activities Sheet:

| id | contact_name | activity_type | description              | date       | outcome  | next_date |
|----|--------------|---------------|--------------------------|------------|----------|-----------|
| 1  | John Smith   | call          | Discussed pricing        | 2026-03-10 | interested | 2026-03-17 |
| 2  | Jane Doe     | email         | Sent product demo        | 2026-03-09 | opened     | 2026-03-14 |
| 3  | Bob Wilson   | meeting       | In-person demo scheduled | 2026-03-08 | scheduled  | 2026-03-15 |

🔌 Step 2: Convert Each Sheet to API with SheetToAPI

This is where the magic happens.

For each sheet you want to query:

  1. Go to SheetToAPI.com
  2. Click "Create New API"
  3. Paste your Google Sheets URL
  4. Select the sheet (Contacts, Companies, Deals, Activities)
  5. Instant REST API for that sheet

You'll get endpoints like:

https://api.sheettoapi.com/api/v1/data/crm-contacts
https://api.sheettoapi.com/api/v1/data/crm-companies
https://api.sheettoapi.com/api/v1/data/crm-deals
https://api.sheettoapi.com/api/v1/data/crm-activities

Why SheetToAPI for CRM?

  • ✅ Instantly queryable with filtering
  • ✅ Clean JSON (no messy parsing)
  • ✅ Built-in pagination (for large contact lists)
  • ✅ Sorting by any column
  • ✅ Search across all fields

💻 Step 3: Build Your CRM Dashboard

Let's build a simple React dashboard to display your CRM data.

Install Dependencies:

npx create-react-app crm-dashboard
cd crm-dashboard
npm install axios recharts date-fns

API Service (services/crmApi.ts):

import axios from 'axios';

const API_KEYS = {
  contacts: 'your_contacts_api_key',
  companies: 'your_companies_api_key',
  deals: 'your_deals_api_key',
  activities: 'your_activities_api_key'
};

const createClient = (sheetName: keyof typeof API_KEYS) => {
  return axios.create({
    baseURL: `https://api.sheettoapi.com/api/v1/data/crm-${sheetName}`,
    headers: { 'X-API-Key': API_KEYS[sheetName] }
  });
};

// Get all contacts
export const getContacts = async (status?: string) => {
  const client = createClient('contacts');
  const url = status ? `?status=${status}` : '';
  const response = await client.get(url);
  return response.data.rows;
};

// Get all deals
export const getDeals = async () => {
  const client = createClient('deals');
  const response = await client.get();
  return response.data.rows;
};

// Get companies
export const getCompanies = async () => {
  const client = createClient('companies');
  const response = await client.get();
  return response.data.rows;
};

// Get activities (sorted by most recent)
export const getActivities = async () => {
  const client = createClient('activities');
  const response = await client.get('?sortBy=date&sortOrder=desc');
  return response.data.rows;
};

// Get deals by stage
export const getDealsByStage = async (stage: string) => {
  const client = createClient('deals');
  const response = await client.get(`?stage=${stage}`);
  return response.data.rows;
};

Contacts Dashboard Component:

import React, { useState, useEffect } from 'react';
import { getContacts } from '../services/crmApi';

interface Contact {
  id: string;
  first_name: string;
  last_name: string;
  email: string;
  company: string;
  status: string;
}

export default function ContactsDashboard() {
  const [contacts, setContacts] = useState<Contact[]>([]);
  const [filteredContacts, setFilteredContacts] = useState<Contact[]>([]);
  const [statusFilter, setStatusFilter] = useState<string>('');
  const [searchTerm, setSearchTerm] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadContacts();
  }, []);

  const loadContacts = async () => {
    setLoading(true);
    try {
      const data = await getContacts();
      setContacts(data);
      setFilteredContacts(data);
    } catch (error) {
      console.error('Failed to load contacts:', error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    let filtered = contacts;

    if (statusFilter) {
      filtered = filtered.filter(c => c.status === statusFilter);
    }

    if (searchTerm) {
      filtered = filtered.filter(c =>
        c.first_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
        c.last_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
        c.email.toLowerCase().includes(searchTerm.toLowerCase())
      );
    }

    setFilteredContacts(filtered);
  }, [statusFilter, searchTerm, contacts]);

  return (
    <div className="p-8 bg-white rounded-lg">
      <h1 className="text-3xl font-bold mb-6">Contacts</h1>

      {/* Filters */}
      <div className="mb-6 flex gap-4">
        <input
          type="text"
          placeholder="Search by name or email..."
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          className="flex-1 px-4 py-2 border rounded-lg"
        />
        <select
          value={statusFilter}
          onChange={(e) => setStatusFilter(e.target.value)}
          className="px-4 py-2 border rounded-lg"
        >
          <option value="">All Statuses</option>
          <option value="lead">Lead</option>
          <option value="prospect">Prospect</option>
          <option value="customer">Customer</option>
          <option value="inactive">Inactive</option>
        </select>
      </div>

      {/* Contacts Table */}
      {loading ? (
        <p className="text-gray-500">Loading contacts...</p>
      ) : (
        <div className="overflow-x-auto">
          <table className="w-full border-collapse">
            <thead>
              <tr className="bg-gray-100">
                <th className="border p-3 text-left">Name</th>
                <th className="border p-3 text-left">Email</th>
                <th className="border p-3 text-left">Company</th>
                <th className="border p-3 text-left">Status</th>
              </tr>
            </thead>
            <tbody>
              {filteredContacts.map(contact => (
                <tr key={contact.id} className="hover:bg-gray-50">
                  <td className="border p-3">{contact.first_name} {contact.last_name}</td>
                  <td className="border p-3">{contact.email}</td>
                  <td className="border p-3">{contact.company}</td>
                  <td className="border p-3">
                    <span className={`px-3 py-1 rounded-full text-sm font-medium ${
                      contact.status === 'customer' ? 'bg-green-100 text-green-800' :
                      contact.status === 'lead' ? 'bg-blue-100 text-blue-800' :
                      contact.status === 'prospect' ? 'bg-yellow-100 text-yellow-800' :
                      'bg-gray-100 text-gray-800'
                    }`}>
                      {contact.status}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      <p className="mt-4 text-gray-600">Total contacts: {filteredContacts.length}</p>
    </div>
  );
}

Sales Pipeline Component:

import React, { useEffect, useState } from 'react';
import { getDealsByStage } from '../services/crmApi';

interface Deal {
  id: string;
  deal_name: string;
  amount: number;
  stage: string;
  probability: string;
  close_date: string;
}

const stages = ['prospect', 'quote', 'proposal', 'contract', 'won', 'lost'];

export default function SalesPipeline() {
  const [dealsByStage, setDealsByStage] = useState<Record<string, Deal[]>>({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadPipeline();
  }, []);

  const loadPipeline = async () => {
    setLoading(true);
    try {
      const pipeline: Record<string, Deal[]> = {};
      for (const stage of stages) {
        const deals = await getDealsByStage(stage);
        pipeline[stage] = deals;
      }
      setDealsByStage(pipeline);
    } catch (error) {
      console.error('Failed to load pipeline:', error);
    } finally {
      setLoading(false);
    }
  };

  const getTotalValue = (deals: Deal[]) => {
    return deals.reduce((sum, deal) => sum + (parseInt(deal.amount) || 0), 0);
  };

  if (loading) return <p>Loading pipeline...</p>;

  return (
    <div className="p-8 bg-white rounded-lg">
      <h1 className="text-3xl font-bold mb-6">Sales Pipeline</h1>

      <div className="grid grid-cols-3 gap-4 mb-8">
        {stages.map(stage => {
          const deals = dealsByStage[stage] || [];
          const total = getTotalValue(deals);
          return (
            <div key={stage} className="bg-gray-50 p-4 rounded-lg border">
              <h3 className="font-bold text-lg capitalize">{stage}</h3>
              <p className="text-2xl font-bold text-blue-600">${(total / 1000).toFixed(0)}k</p>
              <p className="text-gray-600">{deals.length} deals</p>
            </div>
          );
        })}
      </div>

      <div className="space-y-4">
        {stages.map(stage => (
          <div key={stage}>
            <h3 className="font-bold capitalize mb-2">{stage}</h3>
            <div className="space-y-2">
              {(dealsByStage[stage] || []).map(deal => (
                <div key={deal.id} className="bg-gray-50 p-3 rounded border-l-4 border-blue-500">
                  <div className="flex justify-between">
                    <span className="font-medium">{deal.deal_name}</span>
                    <span className="text-green-600 font-bold">${deal.amount.toLocaleString()}</span>
                  </div>
                  <div className="text-sm text-gray-600">
                    Probability: {deal.probability} | Close: {deal.close_date}
                  </div>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

🤖 Step 4: Automate with Zapier

Now let's add automation. Create these Zapier workflows:

Workflow 1: Email Reminder for Upcoming Deals

Trigger: Time reaches 9am (daily)
  ↓
Zapier: Find deals where close_date is in next 3 days
  ↓
Action: Send reminder email to sales team

Workflow 2: Log Activities from Email

Trigger: Email received from customer
  ↓
Zapier: Extract sender, date, subject
  ↓
Action: Create row in Activities sheet

Workflow 3: Slack Notifications

Trigger: New deal added to Sheets
  ↓
Zapier: Check deal amount > $10k
  ↓
Action: Send Slack notification to sales channel

💰 Cost Comparison

PlatformMonthly CostUsersTotal/Year
Salesforce$165/user5$9,900
HubSpot$50/user5$3,000
Pipedrive$14/user5$840
Google Sheets + SheetToAPIFree5$0

Annual savings: $840 - $9,900


📈 Advanced Features

Want to go further? Add these:

  • Forecasting: Use formulas to predict quarterly revenue
  • Reporting: Create dashboard sheets with SUMIF, COUNTIF formulas
  • Team Collaboration: Use Sheets comments for deal notes
  • Integration: Connect to Mailchimp for email campaigns via Zapier
  • Mobile: Build a React Native app using the SheetToAPI endpoints

🎯 When to Move to Professional CRM

Your homemade CRM handles:

  • ✅ Up to 50k contacts
  • ✅ Deal tracking and pipeline
  • ✅ Activity logging
  • ✅ Basic automation via Zapier

When to upgrade:

  • ❌ More than 100k records
  • ❌ Need advanced reporting
  • ❌ Multiple integrations required
  • ❌ Enterprise-level support needed

✨ Conclusion

You've just built a fully functional CRM system for:

  • $0 instead of $840-9,900/year
  • Hours of setup instead of weeks
  • Your team's comfort with Sheets instead of learning new software
  • Complete control over your data

Next steps:

  1. Create your Google Sheet with the structure above
  2. Connect each sheet to SheetToAPI (30 seconds per sheet)
  3. Build your dashboard with the React code
  4. Set up Zapier automations
  5. Start selling smarter 🚀

Your sales team will love managing data in a familiar spreadsheet. Your accountant will love the cost savings. And you'll love the simplicity.

P

Pikebyte.dev

Expert contributor sharing insights and best practices for building with SheetToAPI.

Ready to build?

Start using SheetToAPI today and turn your spreadsheets into powerful APIs.

Explore Documentation