Google Sheets Export & Import
Pricing
from $0.10 / 1,000 row writtens
Google Sheets Export & Import
Send any Apify dataset or JSON to a Google Sheet (append, append only new rows with dedupe, or replace with backup), or import a sheet tab into a dataset. Public sheets import with no credentials. Works as an integration after any scraper.
Pricing
from $0.10 / 1,000 row writtens
Rating
0.0
(0)
Developer
Brandon Ob
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Google Sheets Export & Import: Apify dataset ↔ Google Sheets (append, dedupe, replace, import)
Send any Apify dataset or JSON to a Google Sheet. Add it as an integration after your scraper and every finished run lands in your spreadsheet automatically. Three write modes:
- Append: add all rows below the existing data.
- Append only new rows (dedupe): skip rows whose key columns (e.g.
url,id) are already in the sheet. Perfect for scheduled scrapers that return the same items every day. - Replace: overwrite the tab, after saving a backup of the old contents.
It also works the other way: Import reads a sheet tab into a dataset, so a Google Sheet can feed URLs, keywords or product lists into any scraper. Public sheets import with no credentials at all.
Columns are created for you: nested objects become price.value-style columns, lists are joined into one cell, and new fields are added as new columns at the end without disturbing the ones you already have.
Who it's for
- Anyone running a scraper on a schedule who wants the results in a Google Sheet, not a CSV download.
- Lead generation and sales teams: scrape leads daily and keep one growing, duplicate-free sheet that a VA or CRM import can work from.
- Price and SEO monitoring: replace a "latest" tab on every run, or append a dated history.
- No-code builders: Sheets is the easiest place to hand data to Looker Studio, Zapier, Make, n8n or a client.
Import a Google Sheet into a dataset
- Set Operation to Import.
- Paste the sheet URL. Copy it from the tab you want, so it ends in
#gid=.... - If the sheet is shared as Anyone with the link: Viewer, run it. No Google setup is needed. For a private sheet, add a service account key (steps below) and share the sheet with it (Viewer is enough to import).
Row 1 becomes the field names (change Header row if your headers are lower down). Every row below becomes one dataset item. Empty rows are skipped. Blank or repeated headers get safe names (column_3, name_2). Turn on Nest dot-path columns to rebuild nested objects from columns like price.value, the reverse of what Export does. Public sheets are read through Google's CSV export, so plain numbers come back as numbers and everything else as text. The Sheets API path keeps Google's own types and shows dates as formatted text.
Example input:
{"operation": "import","spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0"}
Feed the dataset into another Actor with its Dataset ID input, or call this Actor from your own code and read the rows over the API.
How to use (export)
1. Create a Google service account (one time, about 5 minutes)
A service account is a robot Google account. You give it access to one sheet, and only that sheet.
- Open Google Cloud Console, create a project (any name), and enable the Google Sheets API for it.
- Go to IAM & Admin > Service Accounts > Create service account. Any name works. Skip the optional role steps.
- Open the new service account, go to Keys > Add key > Create new key > JSON. A
.jsonfile downloads. - Open the file in a text editor, copy all of it, and paste it into Service account JSON in this Actor. It is stored encrypted.
- Copy the
client_emailfrom the file (it looks likename@project.iam.gserviceaccount.com).
2. Share your sheet with the service account
Open your Google Sheet, click Share, paste the client_email, choose Editor, and untick "Notify people". Viewer or Commenter access is not enough. If you forget this step, the run fails with a message telling you exactly which email to share with.
3. Run it
- Paste the Google Sheet URL (or ID).
- Optional: a Sheet (tab) name. A missing tab is created for you.
- Pick the source: a Dataset ID, or paste JSON data.
- Pick a write mode. For append-new-only, set Key columns (e.g.
url).
Use it as an integration after another Actor
- Open your scraper (any Actor or task) and go to the Integrations tab.
- Add Google Sheets Export as an Actor integration, triggered on Run succeeded.
- In its input, fill in the sheet URL, service account JSON, tab and mode. Leave Dataset ID empty (or set it to
{{resource.defaultDatasetId}}): the finished run's dataset is exported automatically.
Now every scheduled run of your scraper updates the sheet. Use Append only new rows so re-scraped items are not added twice.
Example input
{"spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit","sheetName": "Leads","datasetId": "Hk8sX2vqP4aZr1wTn","mode": "append-new-only","keyColumns": ["url"],"excludeColumns": ["html"]}
Example output
A summary row in the dataset and an OUTPUT record in the key-value store:
{"sheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=184019232","sheetName": "Leads","mode": "append-new-only","rowsInSource": 1250,"rowsWritten": 312,"rowsSkippedDuplicates": 936,"rowsSkippedEmptyKey": 2,"newColumnsAdded": ["company.linkedinUrl"],"totalColumns": 18,"tabCreated": false,"firstRowWritten": 4821,"lastRowWritten": 5132,"backupKey": null,"chargeLimitReached": false}
How rows and columns are written
- Row 1 is the header. The first run writes it; later runs match fields to it by name.
- Automatic columns: every field in the data, in first-seen order.
{"price": {"value": 9}}becomes aprice.valuecolumn.["a", "b"]becomesa, b(change the separator in Array separator). Lists of objects are written as JSON text. Fields starting with#(Apify hidden fields) are skipped. - Explicit columns: set Columns to write only those, in that order.
- New fields in later runs are added as new columns at the end. Existing columns are never reordered, renamed or removed.
- Values are written as-is (RAW) by default, so scraped text that starts with
=is never run as a formula. ChooseUSER_ENTEREDif you want Google to parse dates and formulas. - Cells longer than Google's 50,000-character limit are cut off with
….
Reliability
- Batched writes, 500 rows per request by default.
- Automatic retries with exponential backoff on Google rate limits (429) and server errors (5xx), respecting
Retry-After. After the first batch, rows go to exact row numbers, so a retried request can't create duplicate rows. - Checks the 10,000,000-cell limit before writing anything. Google counts every cell in every tab, empty ones included. If the export would not fit, the run stops with nothing written and tells you how to fix it.
- Never clears your sheet unless you choose replace. Replace saves a backup of the old tab (formulas included) to the run's key-value store as
BACKUP-<tab>-<time>, writes the new data, and only then trims leftover rows and columns. - Empty source = no change. If the scraper returned 0 items, nothing is written, even in replace mode, so a failed scrape can't wipe your sheet.
- Clear errors, for example:
Permission denied ... Open the spreadsheet, click Share, and add bot@my-project.iam.gserviceaccount.com as EditorThe Google Sheets API is not enabled for your Google Cloud project. Enable it at https://console.cloud.google.com/apis/library/sheets.googleapis.com?project=...This file is an Excel (.xlsx) file stored in Google Drive, not a Google Sheet. Open it and use File > Save as Google Sheets
Pricing
Pay per event:
| Event | Price |
|---|---|
| Actor start | $0.001 per run |
| Row written | $0.0001 per row ($0.10 per 1,000 rows), for rows written to a sheet (export) or to the dataset (import) |
Only rows actually written are charged. Duplicates skipped by dedupe are free, and so are dry runs: turn on Dry run (or run without credentials) to preview the rows and columns that would be written without touching the sheet. A daily scraper that adds 200 new rows costs about $0.02 per day. Set a maximum cost per run in Apify: the Actor writes as many rows as fit within it, stops cleanly, and reports how many were left out.
Why another Sheets Actor?
Pushing scraper results into Google Sheets is usually a scheduled, unattended job, so the export has to work every time and say clearly what went wrong when it can't. This Actor is built for that: one job (dataset to sheet), retries for Google's rate limits, a cell-limit check before any write, a backup before any overwrite, and dedupe by key columns so scheduled runs don't fill your sheet with copies. It does one thing and tries to do it reliably.
FAQ
Why a service account and not "Sign in with Google"? A service account works in scheduled and integration runs without anyone signing in, never expires because a browser session ended, and only sees the sheets you share with it. If you already have an OAuth app, you can use the OAuth client ID, secret and refresh token fields instead (the refresh token must have the https://www.googleapis.com/auth/spreadsheets scope).
Can it import a Google Sheet into a dataset? Yes. Set Operation to Import. Public sheets need no credentials; private sheets need a service account with Viewer access.
What does dedupe compare? The values in your key columns, as text, against every row already in the tab, plus earlier rows in the same run. Rows where all key columns are empty are skipped (and counted in rowsSkippedEmptyKey), because they can't be told apart and would be added again on every run.
Can I use a key column that is nested? Yes, use the dot path, e.g. product.sku.
Will it change my formatting, formulas or extra columns? Append modes only add rows at the bottom and new header cells at the end of row 1. Replace mode rewrites the whole tab, including columns you added by hand, so keep formulas on a separate tab.
My data has more than 10 million cells. Google Sheets can't hold it. Split by tab or spreadsheet, choose fewer columns, or use replace instead of an ever-growing append.
Something broke or you need a feature? Open an issue on the Issues tab. I usually reply within a day.