Google Sheets CRM Integration: Syncing Salesforce Records, Custom Fields, and Updates
Learn how to build a Google Sheets CRM integration with Salesforce, including record matching, custom field mapping, two way sync, change detection, and conflict handling.
TL;DR
- Google Sheets CRM integration connects a spreadsheet to a CRM like Salesforce through a CRM integration API, so records move between the two without manual copy and paste
- Salesforce API integration typically matches spreadsheet rows to CRM records using a Salesforce Record ID or a custom External ID field, set up before the first sync runs
- Getting connected requires OAuth authentication and scoped permissions on both sides, plus an initial data pull that establishes the field mapping baseline
- Safe CRM data synchronization only writes the fields listed in the mapping, uses upsert operations instead of blind creates, and never touches custom fields outside that mapping
- Ongoing syncs should detect changed records through timestamps or change tracking columns rather than reimporting the full dataset on every run
- When the same field changes in both Google Sheets and Salesforce, the integration needs a clear priority rule and a manual review path for cases that rule cannot resolve
A sales team builds a territory list in Google Sheets. A week later, half the deal stages in that sheet no longer match what is actually in Salesforce, because someone updated the CRM directly and the spreadsheet never heard about it. This is the gap Google Sheets CRM integration is meant to close: instead of a spreadsheet that drifts out of date the moment anyone touches the source system, records, custom fields, and updates move between the two automatically through a CRM integration API.
Doing this well takes more than a one time export. It means choosing how records are matched between systems, mapping fields carefully enough that a sync does not silently overwrite someone's work, detecting what actually changed instead of reimporting everything on every run, and deciding what happens when the same field gets edited on both sides at once. This guide walks through a Salesforce API integration from that first connection through the conflict handling that keeps a two way sync trustworthy over time.
Understanding Google Sheets CRM Integration and the Role of a CRM Integration API
Google Sheets CRM integration is the practice of connecting a spreadsheet to a CRM platform, most often Salesforce, so that records, fields, and updates move between the two systems automatically instead of through manual copy and paste. The piece that makes this possible is a CRM integration API: an interface, like the Salesforce REST API or Bulk API, that exposes CRM objects and fields as structured endpoints a script, spreadsheet add-on, or integration layer can read from and write to.
Teams reach for this setup for reasons that have little to do with replacing the CRM itself. Sales operations often needs an editable, shareable view for territory planning, discount approval workflows, or ad hoc lists that do not map cleanly onto Salesforce's standard views. Marketing and support teams build campaign or account lists in a spreadsheet because it is faster to filter and share than a saved report. And plenty of people who touch CRM data regularly, finance, leadership, partners, do not have or need a full Salesforce license, so a synced spreadsheet becomes their access point instead.
A CRM integration API is what keeps this connection reliable rather than a one time export. It handles a few jobs a plain CSV export cannot:
- Authentication: confirming the request is coming from an authorized connection, not an open door to CRM data
- Metadata access: exposing which objects and fields exist, their data types, and any validation rules or picklist values attached to them
- Structured operations: supporting create, read, update, delete, and upsert calls instead of one flat export
- Field level security: respecting the same permission model Salesforce enforces for a logged in user, so the integration cannot read or write fields it should not
This is the same request and response pattern behind how API integration works more broadly: a Google Sheets Salesforce integration just applies it with a CRM integration API on one side and Google's own Sheets API on the other.
Planning Record IDs, Field Mappings, and Sync Direction for Salesforce API Integration
Before writing a single line of a Salesforce API integration, three decisions determine whether the sync stays reliable six months from now: which field uniquely identifies a record, how spreadsheet columns map to Salesforce fields, and which direction data is allowed to flow.
Record identification comes down to a short list of options:
- Salesforce Record ID: the platform's own 18 character identifier, the most reliable match but not something anyone types by hand
- External ID field: a custom field flagged as an External ID in Salesforce, often populated with a value from the spreadsheet or a source system, and the most common choice for spreadsheet based syncs
- Business fields like email or account name: easy to read but risky, since these values can change or repeat across records
Field mappings should be written out as a simple list before any code runs: spreadsheet column A maps to Salesforce field X, column B maps to field Y, and so on. Two details matter here. First, decide which mapped fields are writable versus read only, since some fields, like a calculated rollup, should never accept a write from the spreadsheet. Second, match data types and picklist values exactly: a status column in the sheet needs to contain the same values Salesforce's picklist accepts, or every update will fail validation.
Sync direction is the last decision, and it shapes everything downstream:
- One way, Salesforce to Sheets: the spreadsheet is a read only reporting view, the safest option to build and maintain
- One way, Sheets to Salesforce: the spreadsheet is the data entry point, common for bulk imports or list building
- Two way: both systems can originate a change, which is powerful but requires the conflict handling covered later in this guide
How to Integrate Google Sheets With a CRM System
Integrating Google Sheets with a CRM system comes down to three steps before any record actually moves: authenticate against both platforms, scope permissions tightly, and run an initial pull to establish a baseline.
On the Salesforce side, this starts with a Connected App, which issues a Consumer Key and Consumer Secret and defines the OAuth flow the integration will use. A web server OAuth flow works well for anything a person authorizes interactively, while a JWT bearer flow suits a scheduled, service to service sync that runs without a user present. Either way, the connected app's OAuth scopes should be limited to what the sync actually needs, typically api and refresh_token, rather than every scope available.
On the Google side, the Sheets API needs to be enabled in a Google Cloud project, and the integration authenticates either through OAuth consent, where a person grants access to their own sheet, or a service account with domain wide delegation for unattended jobs. A service account is generally the better fit for a scheduled sync that should not depend on one person's login staying valid.
Permissions deserve more attention than they usually get:
- In Salesforce, grant the integration user's profile or permission set field level security only for the objects and fields being synced, not blanket access to the org
- In Google Sheets, share the specific spreadsheet with the service account or app, rather than granting Drive wide access
- Treat every added scope as a question: does the sync actually need this, or is it just convenient to have
Once authentication and permissions are in place, the initial data access run pulls existing Salesforce records into a sheet tab, usually scoped to a specific list view or report filter rather than an entire object, so the first sync does not try to pull an entire org's history into one spreadsheet. This first pull also doubles as a check that the field mapping actually works before any writes happen in either direction. Getting this connection layer right the first time avoids revisiting the credential management practices that keep token refreshes and scope changes from quietly breaking a sync months later.
How to Synchronize Google Sheets and Salesforce Records While Preserving Custom Fields and Preventing Duplicate Updates?
Synchronizing records safely comes down to three habits: matching on a stable identifier before writing anything, sending only the fields defined in the mapping, and using upsert operations instead of blind creates.
Matching records should always happen before any write. The integration looks up the incoming row's identifier, a Record ID or External ID, against Salesforce. A match found means update the existing record. No match means create a new one. Skipping this lookup, or matching on an unreliable field, is the single most common cause of duplicate CRM records.
Preserving custom fields means the sync only ever writes what is explicitly listed in the field mapping. A partial update payload, containing just the mapped fields, leaves every other field on the record, including custom fields the sync does not know about, untouched. A full record overwrite, by contrast, can silently blank out custom fields that another team or automation populated after the last sync.
Preventing duplicate updates relies on the upsert pattern built into the Salesforce REST API. Instead of separate create and update calls, an upsert request targets a record by its External ID field, and Salesforce decides whether to insert or update based on whether that value already exists:
PATCH /services/data/v67.0/sobjects/Contact/Sheet_Row_Id__c/SHEET-00231
{
"Email": "[email protected]",
"Deal_Stage__c": "Negotiation"
}
Note what is missing from that payload: any field not listed, including custom fields, stays exactly as it was in Salesforce. Setting a uniqueness constraint on the External ID field in Salesforce adds a second layer of protection, rejecting the write outright if a duplicate identifier ever slips through. This is the same duplicate prevention problem that shows up anywhere an integration retries a failed call: the fix is an identifier that makes the operation idempotent, not a check that happens after the fact.
Detecting and Applying Ongoing Record Changes in Google Sheets Salesforce Integration
Detecting ongoing changes means each sync run only touches records that actually changed since the last run, rather than pulling and comparing the entire dataset every time. Reimporting everything on a schedule works fine for a few dozen rows. It stops working, and starts burning API quota, well before a spreadsheet reaches a few thousand.
On the Salesforce side, there are a few practical ways to scope a query to what changed:
- A SOQL query filtered by LastModifiedDate, compared against the timestamp of the last successful sync
- Change Data Capture, which publishes near real time change events for subscribed objects, useful when a spreadsheet needs to reflect Salesforce updates quickly
- A custom Last_Synced__c field on each record, updated after every successful push, which doubles as an audit trail
On the Google Sheets side, the equivalent is a helper column: a last synced timestamp per row, or a dirty flag set by an Apps Script onEdit trigger whenever someone changes a cell. Either approach lets the next sync run query only the rows flagged since the last pass, using the same Sheets API access patterns other automated systems rely on to read spreadsheet data efficiently.
Processing only the delta keeps a sync job fast, keeps it well inside Salesforce and Google Sheets API rate limits, and lowers the odds of an accidental overwrite, since fewer records are in motion during any single run.
Managing Conflicting Updates Between Google Sheets and Salesforce
A conflict happens when the same field on the same record changes in both Google Sheets and Salesforce between one sync run and the next. Two way syncs eventually run into this, and how the integration handles it determines whether people trust the spreadsheet or stop using it.
Detecting a conflict starts with comparing timestamps: if the Salesforce record's LastModifiedDate and the sheet row's last edited time both moved past the previous sync, and the field values disagree, that is a conflict rather than a simple one sided update.
From there, a few priority rules are common:
- Last write wins: whichever system recorded the more recent timestamp for that field takes precedence
- Source of truth per field: some fields, like deal stage, are only ever edited in Salesforce, while others, like internal notes, are only ever edited in the sheet, which removes the ambiguity entirely
- Most recent non null value: useful when one side left a field blank and the other populated it
Not every conflict should resolve automatically. When both systems changed a field and no priority rule clearly applies, or when the field is sensitive enough that an incorrect automatic choice would cause real problems, the safer move is to flag the row and route it for manual review rather than guessing. Logging every conflict, with the old value, the new value, the source, and the timestamp, gives whoever reviews it enough context to decide quickly, and gives the team a record to point to when someone asks why a field changed.
Building this kind of Google Sheets CRM integration from scratch means solving OAuth for two separate platforms, field mapping, deduplication, and conflict handling before any sync logic actually runs. Corsair is an open source integration layer for AI agents and internal tools that handles authentication, permissions, and API connections for Salesforce, Google Sheets, and hundreds of other services, so teams can build the sync logic instead of the plumbing underneath it. It is Apache 2.0 licensed, available self hosted or through a hosted Hub, and its free Hobby plan includes unlimited tool calls and up to 50 connections with no credit card required. Learn more at corsair.dev.
Frequently Asked Questions
Can Google Sheets sync with Salesforce without custom code?
Yes, no code and low code connectors can handle basic field syncs. But preserving custom fields, matching records by External ID, and handling conflicts usually need more control than a no code tool exposes, which is why many teams move to a purpose built Salesforce API integration once the sync grows past a simple one way export.
Should I use the Salesforce REST API or Bulk API for a Google Sheets sync?
The REST API is a good fit for a spreadsheet with up to a few thousand rows and frequent, small syncs. The Bulk API becomes the better choice once volume climbs into the tens of thousands, since it processes upserts in asynchronous batches instead of one call per row.
How do I stop a sync from overwriting a custom field that's only edited in Salesforce?
Leave it out of the field mapping entirely. A properly built CRM data synchronization job only sends the fields explicitly listed in the mapping, so anything excluded stays untouched on both sides.
What's the safest way to identify which Salesforce record a spreadsheet row belongs to?
A dedicated External ID field, populated once when the record is first created and never edited afterward, is far more reliable than matching on a name or email address, both of which can change or repeat across records.
How often should Google Sheets and Salesforce sync run?
It depends on how costly stale data is. A reporting spreadsheet can sync hourly or a few times a day without issue. An operational list feeding active sales work may need syncs every few minutes, as long as the schedule stays within Salesforce and Google Sheets API rate limits.