Build a Free CRM System with Google Sheets and SheetToAPI in 2026
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:
| Issue | Typical CRM | Google Sheets + SheetToAPI |
|---|---|---|
| Monthly Cost | $50-200 per user | Free |
| Setup Time | Weeks of training | Hours |
| Customization | Limited to templates | Unlimited |
| Team Adoption | High learning curve | They already know Sheets |
| Data Lock-In | Hard to export | It'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 IDfirst_name,last_name- Contact detailsemail,phone- Contact infocompany- Company they work forstatus- lead, prospect, customer, inactivecreated_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:
- Go to SheetToAPI.com
- Click "Create New API"
- Paste your Google Sheets URL
- Select the sheet (Contacts, Companies, Deals, Activities)
- 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
| Platform | Monthly Cost | Users | Total/Year |
|---|---|---|---|
| Salesforce | $165/user | 5 | $9,900 |
| HubSpot | $50/user | 5 | $3,000 |
| Pipedrive | $14/user | 5 | $840 |
| Google Sheets + SheetToAPI | Free | 5 | $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:
- Create your Google Sheet with the structure above
- Connect each sheet to SheetToAPI (30 seconds per sheet)
- Build your dashboard with the React code
- Set up Zapier automations
- 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.
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
