No credit cardStart free
·12 min read·How-to

How to Export HubSpot Data: A Guide

Contacts, deals, companies, lists, reports, property history, and continuous sync.


rawquery itself doesn't use HubSpot. Our stack is Umami for analytics and Paddle for billing: two tools, one invoice each, no CRM yet. But HubSpot is what most of the people who try our product run, so we tested every export path against a HubSpot account we have through another project, and joined it with that project's Stripe data inside a rawquery workspace.

There are three reasons people want HubSpot data out of HubSpot:

  1. Backup: before churning, before a CRM migration, or just to sleep at night.
  2. Analysis: HubSpot's reports stop the moment you want to join with Stripe charges or filter on a property they didn't pre-bake into a dashboard.
  3. Continuous sync: because a CSV is a snapshot, and your warehouse needs the data fresh.

What HubSpot's native export gives you (and what it doesn't)

HubSpot has two native export paths. They are not interchangeable.

  1. Per-object export from inside Contacts, Companies, Deals, Tickets etc. You choose columns, you get a CSV/XLSX by email. Works on the free CRM. This is what most people mean when they say "export HubSpot data".
  2. Account-wide export from Settings → Account Defaults → Privacy & Consent → Export your account data. One zip, everything in it. Required by GDPR for data portability. Only one paid seat can trigger it.

Neither gives you property history. Neither gives you the associations between objects in a way that's easy to rejoin. The account-wide one drops a folder structure that takes more effort to make sense of than hitting the API directly.

What you wantNative exportAPINotes
Current property valuesYesYesThe easy part
Custom propertiesYesYesAdd them to the view first
Property change historyNoYes/properties-history endpoint
Associations (deal → contact, contact → company)PartialYesNative export gives IDs only, no labels
Engagement timelineNoYesCalls, meetings, emails, notes
Email event logNoYesMarketing side
List membership snapshotYesYesStatic and dynamic lists
List rules (the filter logic)NoYesThe rule that defines a dynamic list
Free CRM throughput1 file / 3 min, async, emailed when ready

The per-object export works the same way on every plan. The hard limit is one export at a time per user, queued and emailed when ready. HubSpot doesn't publish a row cap; community reports put it in the high tens of thousands, with delivery taking 10 to 20 minutes at that volume.

HubSpot contacts export modal showing format choice, language, and what's included
HubSpot's export modal. Format, language, and what gets included. No column picker here.

Exporting contacts from HubSpot

The path: CRM → Contacts → Export (the button is right next to Import and Create contact in the top-right of the contacts list). The export is scoped to your current view, so a saved view filter is the easiest way to export a subset, and the columns it produces are the columns shown in that view. Format choice (CSV, XLS), language, and the Customize options round out the modal.

Archived contacts are excluded by default. If you're exporting for backup, switch the view filter to include them, or you'll be missing rows you didn't know you had.

There's no column picker inside the export modal. Go back to the view, click Edit columns, set what you need, then export. Custom properties are at the bottom of that picker, alphabetically.

Inside the modal, the Customize section has the choice that controls what you actually get: Properties and associations in your view vs All properties on records. The first uses the columns from your view (typically 5 to 15). The second ignores the view and dumps every property HubSpot has for each row (often 200 to 400+). Use the second for backups, the first for analysis.

HubSpot export modal Customize section showing properties and associations options
The Customize panel. "View" vs "all properties" is the toggle that decides whether your CSV has 12 columns or 400.

For very large contact bases (above ~100k), the email export still works but you'll want to script against the API instead. Paginate /crm/v3/objects/contacts with limit=100 and follow the paging.next.after cursor. The API is the only path that gives you the full row count up front.

Exporting deals, companies, and tickets

Same UI pattern, different object. The trap is associations. A deals export gives you the deal record and its primary associated contact and company as IDs, not labels, not the full association graph, and not the multi-association case (a deal with two contacts).

If your follow-up question is "which contacts are on which deals", the per-object export will mislead you. Export contacts and deals separately and join them yourself in a spreadsheet, or hit the associations endpoint:

bash
curl --request GET \
--url 'https://api.hubapi.com/crm/v4/objects/deals/{dealId}/associations/contacts' \
--header 'authorization: Bearer YOUR_PRIVATE_APP_TOKEN'

Tickets work identically. Custom objects work identically too: same export button, same view-driven columns, same association quirk.

Exporting property history

Property history (every change to a property over time, with timestamp and source) is not in the UI export. Not as an option, not as a checkbox, not as a separate menu. If you want to know when a deal moved from Qualified to Proposal sent, the CSV will give you the current stage and that's it.

The only path is the API. HubSpot exposes propertiesWithHistory as a query parameter on object reads:

bash
curl --request GET \
--url 'https://api.hubapi.com/crm/v3/objects/deals/12345?propertiesWithHistory=dealstage,amount' \
--header 'authorization: Bearer YOUR_PRIVATE_APP_TOKEN'

The response includes a propertiesWithHistory object: each property name maps to an array of {value, timestamp, sourceType, sourceId} entries, oldest to newest. Pagination is per-property: the array stops at 100 entries by default. For high-churn properties on long-lived records, you have to request the same record multiple times with the archived and pagination flags.

This matters for two real questions: deal velocity (time spent in each stage), and attribution decay (when a lifecycle stage changed relative to a campaign send). Neither has a native HubSpot dashboard. Both are one SQL query away once the history is in a warehouse.

Exporting lists, including the rule that defines them

Lists in HubSpot are either static (membership frozen at creation) or active (re-evaluated against a filter rule whenever a contact changes). Both can be exported from the list view, same way as contacts.

The catch: an active list export gives you a snapshot of who is on the list right now, not the rule that defines it. If you're backing up your HubSpot setup before a migration and you have 80 active lists, the CSVs will not preserve any of the filter logic. The rule lives in the API:

bash
curl --request GET \
--url 'https://api.hubapi.com/crm/v3/lists/{listId}' \
--header 'authorization: Bearer YOUR_PRIVATE_APP_TOKEN'

The response includes filterBranch, the filter tree as JSON. Save this if you want to reproduce the list elsewhere.

Exporting reports (CSV, Excel, PDF)

From any saved report: ⋯ menu → Export. Three formats: CSV, XLS, PDF. PDF gives you the rendered chart; CSV/XLS give you the underlying rows the report aggregates.

Report exports are useful for sending the rendered output to someone who's not in HubSpot. They're a poor source for downstream analytics: the rows are pre-aggregated by the report's grouping. If you want flexibility, export the underlying objects, not the report.

Sending HubSpot data somewhere useful

Once you have the CSV (or the API JSON), what do you do with it? The path depends on the destination.

Excel or Google Sheets

Open the CSV. Done. The only gotcha: HubSpot quotes commas inside fields correctly, but some property values contain newlines (notes, free-text custom properties), which Excel handles fine but Google Sheets occasionally splits across rows. If your row count after import doesn't match the export, that's why. Open in Excel, save as XLSX, then upload.

PostgreSQL or Redshift

Three options, in increasing order of effort and decreasing order of long-term pain.

  1. Manual COPY FROM the CSV into a Postgres table. Works for one-off backups. Breaks the moment HubSpot adds a column.
  2. A scheduled Python script that paginates the HubSpot API and upserts into Postgres. You own the schema drift, the rate limit handling, the property history pagination, and the on-call when HubSpot changes the API. Works, but you're building a connector.
  3. Fivetran, Airbyte, or rawquery: a managed connector. Fivetran prices on monthly active rows (MAR); their own example for a 1-200 employee company on the Standard plan lands at $549/month, scaling with the objects you sync. Airbyte OSS is free if you self-host, but you're running the infrastructure. rawquery lands the data in Iceberg on S3 and lets you query it without a warehouse on top.

Tableau or Power BI

Both have native HubSpot connectors. Both work for small accounts. Both struggle with property history and large engagement volumes because the connectors pull on-demand and don't cache. At any non-trivial scale, the standard pattern is HubSpot → warehouse → Tableau/Power BI on top of the warehouse.

JSON

The API returns JSON natively. curl | jq gets you most of the way for one-off pulls. For repeated pulls, the same logic as Postgres: at some point you want a managed connector instead of a script you maintain.

Doing it continuously

Every method above is a snapshot. The CSV you exported yesterday is wrong today. The dashboard your CFO is looking at is from last Tuesday.

For continuous sync, you have three real choices:

OptionSetupMaintenanceCostSchema drift
Custom Python + cron2 to 5 daysYouEngineer timeYou handle it
Fivetran30 minFivetran$549/mo (their published Standard example)Fivetran handles it
Airbyte (self-hosted OSS)1 dayYou (infra)Hosting + your timeAirbyte handles it
rawquery5 minrawqueryFlat, predictablerawquery handles it

Fivetran wins on connector breadth (300+) and on a UI that data teams already know. It loses on pricing: MAR means the bill grows with your CRM whether you're querying that data or not. Airbyte wins on cost if you're comfortable running it, and loses on the "running it" part.

rawquery is the right pick under ~10 TB, in EU jurisdiction, when you want to query without a warehouse on top.

What it looks like in rawquery

We connected the HubSpot account to a rawquery workspace and pointed Stripe at the same workspace.

Connect HubSpot

HubSpot uses OAuth, so the connection is created from the dashboard rather than the CLI. Connections → Add Connection → HubSpot → Connect with HubSpot. You authorize on HubSpot's side, you land back on rawquery, the connection is live. Five clicks, no JSON config.

rawquery Connect HubSpot dialog with the 5-step wizard and Connect with HubSpot button
The HubSpot OAuth dialog in rawquery. Five steps: Credentials, Test, Tables, Schedule, Done.

Sync

After OAuth lands you back on rawquery, the wizard's Tables step lists every HubSpot object you can sync: contacts, companies, deals, tickets, engagements, email events, lists. You pick what you need, you set a schedule (hourly, daily, weekly, or on-demand), and you're done. The data lands as Iceberg tables on S3 within a few minutes.

From the CLI, you can trigger an on-demand sync any time:

bash
rq connections sync hubspot

Query

Pipeline by close month, plain SQL:

sql
SELECT
DATE_TRUNC('month', closedate)::DATE AS month,
COUNT(*) AS deals,
ROUND(SUM(amount), 0) AS pipeline_value
FROM hubspot.deals
WHERE closedate >= '2025-01-01'
GROUP BY 1
ORDER BY 1 DESC

In the raw export, dealstage is HubSpot's internal numeric ID. Join the pipelines API to translate IDs to labels, or skip the issue by aggregating on closedate.

rawquery query UI showing HubSpot deals pipeline value aggregated by close month
The query UI in rawquery, against a synced HubSpot.

And the query HubSpot reports can't do, because half the answer lives in Stripe: how many of your HubSpot contacts at each lifecycle stage are actually paying.

sql
SELECT
c.lifecyclestage,
COUNT(DISTINCT c.id) AS hubspot_contacts,
COUNT(DISTINCT s.id) AS with_stripe_account
FROM hubspot.contacts c
LEFT JOIN stripe_live.customers s
ON LOWER(s.email) = LOWER(c.email)
GROUP BY 1
ORDER BY hubspot_contacts DESC
rawquery query result joining HubSpot contacts to Stripe customers by email, grouped by lifecycle stage
Saved as a query, callable from the API.

That query does not need a Snowflake warehouse on top of a Fivetran sync to a dbt-modelled fact table. Same SQL, two synced sources, one workspace. HubSpot lifecycle stages and actual Stripe customer status drift apart over time.

Where rawquery is weaker

We have eight built-in connectors today (Postgres, MySQL, Stripe, HubSpot, Salesforce, Shopify, Google Sheets, plus a generic HTTP connector for anything else via JSON spec). Fivetran has 300+. If you need Marketo, Zendesk, NetSuite, or one of the long tail of B2B SaaS connectors, Fivetran or Airbyte will get you there faster.

Free CRM data export limits

Yes, the free CRM lets you export. The per-object export works. You don't get the account-wide GDPR export (paid plans only), and the API is rate-limited to 100 requests per 10 seconds on the free tier.


rawquery syncs HubSpot, Stripe, and Postgres into one Iceberg workspace you can query in standard SQL. Try it free.