BlogResources

How to connect HubSpot to Google Sheets without exporting the duplicates too

Connect HubSpot to Google Sheets without duplicates: clean records before export and keep the sheet from becoming a second CRM.

Rebecca PearsonRebecca Pearson11 min read

Summarize with AI

How to connect HubSpot to Google Sheets without exporting the duplicates too
On this page

To connect HubSpot to Google Sheets without duplicates, clean the records before they leave HubSpot: exclude internal and test records, flag probable duplicates, pick one row grain, and write the exclusion counts into the sheet. An export inherits every problem in the portal it came from, so the cleaning belongs upstream of the sheet.

If the same company exists four times in HubSpot, it exists four times in the spreadsheet, and the total at the bottom of the column is wrong. A sheet should contain a smaller, cleaner set than the portal holds, and deciding what to exclude is the part worth planning.

What we'll cover

Key takeaways

  • Duplicate, test and internal records in HubSpot become wrong totals in the sheet, so exclude them before export.
  • Choose one row grain (per deal, contact or company) from the question, and state it in the sheet.
  • Flag probable duplicates instead of merging them, and fix them in HubSpot.
  • Write the run time and the exclusion counts into the sheet so readers can explain differences from the portal.
  • Move to incremental exports only when full exports become impractical, and reconcile with a periodic full export.

The export inherits the mess

An export carries every data-quality problem in the portal into the sheet. Five problems cause most wrong totals:

  • Duplicate contacts and companies become duplicate rows, so any count or sum is wrong by the number of duplicates.
  • Test and internal records (colleagues, test submissions, competitors someone added) distort the numbers people quote.
  • Half-populated records leave columns empty for many rows, and a spreadsheet average treats the blanks as zero.
  • Deals with no associated company disappear from company-level grouping without warning.
  • Records created by integrations follow different conventions from records created by people, which produces two spellings of everything.

Cleaning in the spreadsheet after export is the wrong order. The cleaning has to be repeated on every export, and eventually someone skips it.

Associations do not fit a rectangle

HubSpot stores objects joined by associations, and the meaning sits in the joins. A sheet is flat, so you must choose a grain: one row per deal, per contact or per company. Everything else becomes a summary column or a separate sheet.

  • A contact can be on several deals, so "one row per contact" and "one row per deal" are different sheets with different totals.
  • Association labels matter. A decision maker and a technical contact are different relationships, and an export that ignores labels flattens them.
  • Pick the grain from the question. A pipeline report is one row per deal, a contactable list is one row per contact, and a revenue-by-account report is one row per company.
  • State the grain in the sheet. The reader cannot tell by looking and will assume whichever grain suits their question.

Building it with CodeWords

To build the export in CodeWords, tell Cody, the automation builder, which HubSpot objects, filters and properties to export and which sheet to write to. CodeWords connects to 3,000+ integrations (CodeWords integrations page, 2026), and you make each connection once and reuse it.

  1. Open CodeWords and start a new automation.
  2. Describe in plain language what should happen: which objects, which filters, which properties, and which sheet.
  3. Authorize a HubSpot private app scoped to the objects involved, plus access to the target spreadsheet.
  4. Describe the exceptions: a probable duplicate, an internal or test record, a property whose options have changed.
  5. Run it against a small, known set of records and check the totals before circulating anything.

You describe the outcome, and Cody builds it, connects it and deploys it. The Free plan covers light use. Pro costs $39 per month and Business costs $100 per month (CodeWords pricing page, 2026). Details are on the pricing page.

What the sheet should contain

The sheet should contain fewer columns than the portal holds, chosen for the question. A dozen properties chosen for the question beat eighty exported because they exist.

  • Properties people will filter on, named readably, not by internal names.
  • A stable identifier, stored as text, so rows can be matched against a later export.
  • Derived values computed before export, such as days in stage, age and whether a field is populated, in place of spreadsheet formulas that someone will break.
  • A flag for excluded records, or a visible count of them, so the reader knows the sheet is filtered and not complete.
  • Nothing that will not be used. Every extra column is a column someone sorts by accident.

Cleaning on the way out

Cleaning on the way out makes the export worth trusting. Five steps matter:

  1. Exclude internal domains and known test records. This is the single largest improvement to most CRM reports.
  2. Flag probable duplicates instead of merging. Merging is destructive and near-matches are sometimes different records. A flag lets the reader decide and gives someone a list to fix in HubSpot.
  3. Normalise before comparing. Lowercase, trim, strip punctuation from phone numbers and remove common company suffixes, or two spellings count as two companies.
  4. Report the counts. Write how many records were excluded and why into the sheet, so the difference from the portal's total is explainable.
  5. Fix it in HubSpot too. An export that quietly works around bad data lets the bad data persist, so the exclusion list doubles as a work list.

Keeping the sheet from becoming a second CRM

A sheet becomes a second CRM when people start editing it, and the edits then disappear. Exports are read-only by nature, yet people add notes and statuses in spare columns anyway.

  • Edits are lost on the next run, which teaches people the sheet is unreliable and makes them stop refreshing it.
  • Decide where annotation lives. Either use a separate column range the export never touches, or store the annotation in HubSpot as a property.
  • Never make the sheet the source of truth for anything the CRM should own, because nothing enforces it and nothing syncs back.
  • Show the run time. A stale CRM export in a board pack is a common and avoidable error, so put the run time in the sheet.

Properties that change under you

Properties change under an export because portals are heavily customised, and whoever manages operations adds, renames and deprecates properties.

  • Match on internal property names, not labels, since people edit labels.
  • Read property definitions instead of hard-coding dropdown options. A value that no longer exists reads as blank.
  • Handle the deleted property. An export that references one should report it clearly, not produce a column of empties.
  • Watch calculated and rollup properties, which can lag or stay empty for records that have not been recalculated.

Incremental exports, once the portal is large

Switch to incremental exports when a full export becomes impractical, and make the switch before it becomes urgent. An incremental export pulls only records changed since the last run, using the last-modified property.

  • Store the cursor after a successful run, not before, so a failure part way does not skip records permanently.
  • Expect invisible deletions. A modified-since query cannot see a deleted record, so an incremental sheet keeps rows that no longer exist unless something reconciles.
  • Reconcile periodically. A monthly full export, compared against the incremental sheet, catches deletions and anything the cursor missed.
  • Merge by identifier, not row position, so an update corrects the existing row.
  • Report rows added, updated and unchanged. A run that updates nothing usually means the cursor is wrong.

Making it survive

A durable export uses filtered search, batching and replacement instead of appending:

  • Use search with filters and pagination, not a full listing filtered afterwards.
  • Batch where volume warrants, since batch endpoints are kinder to rate limits.
  • Replace a named range instead of appending, or retries duplicate rows.
  • Write the run time, the grain and the exclusion counts into the sheet.
  • Report the outcome: records exported, excluded and flagged as possible duplicates.

Limits worth knowing about

Five limits shape a HubSpot export. Check HubSpot's current documentation for exact figures, which depend on your plan.

  • Rate limits depend on your HubSpot subscription, so a full export needs pagination and pacing.
  • Search is eventually consistent, so a record created and immediately searched for may not appear.
  • Google Sheets has a cell limit, which a wide export of a large portal can reach.
  • Some properties cannot be used as search filters, which changes how you select records.
  • Association retrieval is capped per call, so a contact on many deals needs paging.

What to set up first

Set up one report at one grain, with internal and test records excluded, probable duplicates flagged, and the run time and exclusion counts written into the sheet. Start with whichever export someone currently produces by hand, because it already has an audience and a definition.

Two habits make the difference. Exclude internal domains from the first version, since that one change corrects most CRM reports more than any other. And write the exclusion counts into the sheet, because the first question anyone asks is why the total differs from the portal's.

Frequently asked questions

Why does the sheet total differ from HubSpot?

The difference comes from duplicates, test records and filtering. Write the exclusion counts into the sheet so the gap is explainable. This is the first question anyone asks, and an unexplained gap discredits the whole report.

Should duplicates be merged during export?

No, flag them instead. Merging is destructive, and near-matches are sometimes different people or companies. A flagged list also gives someone a work list for fixing the portal, which is the actual fix for the duplicates.

Should the sheet have one row per contact or per deal?

Use whichever grain matches the question, and state it in the sheet. A contact can be on several deals, so the two grains produce different totals, and a reader will assume the grain that suits their own question.

Why are some columns mostly empty?

Those properties are optional and half-populated, which is normal in any CRM. A spreadsheet average over that column treats blanks as zero, so report the fill rate alongside anything computed from it.

Can people edit the exported sheet?

They will, and the edits vanish on the next run. Give them a column range the export never touches, or store the annotation in HubSpot where it belongs. The sheet should never become the source of truth.

Why did a column go blank after working for months?

A property was renamed, deprecated or had its options changed. Match on internal property names instead of labels, and have the export report a missing property clearly instead of producing a column of empty cells.

What is the single biggest improvement to a CRM export?

Excluding internal domains and test records. It is a short filter, it corrects the numbers people quote most often, and almost no export applies it by default.

Should the export be full or incremental?

Use a full export while the portal is small, because it is simpler and corrects itself. Switch to incremental when full exports become impractical, with a stored cursor, identifier-based merging and a periodic full reconciliation to catch deletions.

Why does an incremental sheet keep records that were deleted?

A modified-since query cannot see deletions, because the record simply stops being returned. Only a periodic full comparison catches them, which is the main reason to keep a full export even after moving to incremental.

Does exporting contacts to a spreadsheet raise privacy questions?

Yes. The export creates a copy outside HubSpot's access controls and deletion process, so a later erasure request will not reach it. Export only the columns the question needs, and check who the sheet is shared with.

How do I stop a stale export being circulated?

Put the run time in a visible cell on every run. A CRM export from three weeks ago looks identical to a current one, which is how an out-of-date number ends up in a board pack.

Get started today

Your first workflow is free to build.

Describe what you need. Cody handles the build, the connections, and the deployment.