Dataset to Google Sheets Sync (append, replace, upsert)
Pricing
from $0.002 / sync run
Dataset to Google Sheets Sync (append, replace, upsert)
Send any Apify dataset to a Google Sheet without OAuth: share the sheet with a service account, then append, replace or upsert by key. Retries Google rate limits, dedupes, never fails on an empty dataset.
Pricing
from $0.002 / sync run
Rating
0.0
(0)
Developer
Tenzin Phuntsok
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Send the results of any Apify Actor to a Google Sheet, on every run, without connecting your Google account. Share your sheet with one email address, paste the sheet link, and choose append, replace, or upsert by key. Built for scheduled and webhook-triggered runs: it retries Google's rate limits, removes duplicates, adds new columns on its own, and never fails just because a scrape came back empty.
What does Dataset to Google Sheets Sync do?
It reads an Apify dataset (the output of any Actor run) and writes the items as rows into one tab of your Google Sheet.
- Append: add the new rows under the existing ones.
- Replace: clear the tab and write everything fresh.
- Upsert: match rows on a key column such as
urlorid, update the ones that exist, add the rest. Optionally delete sheet rows that are no longer in the data, so the tab mirrors the dataset.
Nested fields become dotted columns (seller.name), arrays are stored as JSON text in one cell, and new fields are added as new columns on the right. Columns you keep by hand (a Notes or Status column, say) are never touched by an upsert.
Why use it instead of the free Google Sheets integration?
The free integration connects through a Google login that expires and then silently breaks scheduled runs, and it stops with an error when a run produces no items. This Actor uses a service account instead. Nothing expires, nobody has to log in, and it works the same from a schedule, a webhook, the API, or an AI agent.
- No Google login, no consent screen, no token that dies after a week.
- Google rate limits (HTTP 429) and outages are retried with back-off instead of failing the run.
- Zero items is a success, not a failure. Nothing is written, an existing header stays, and the run reports
rowsWritten: 0. An empty scrape never deletes rows, even withdeleteMissingon. - Upsert by one or more key columns, with duplicate removal (last occurrence wins).
- Big datasets are written in chunks under Google's request size limits; the run summary tells you exactly what happened.
How to send Apify data to Google Sheets
-
Share your sheet. Open your Google Sheet, click Share (top right), paste this email and give it Editor access:
apify-sync@apify-sheets-sync-509921.iam.gserviceaccount.com(Or use your own service account: see "Bring your own Google service account" below.)
-
Paste the sheet link into the Google Sheet link field. Copy it straight from your browser's address bar.
-
Pick the data. Choose the Dataset to send, or leave it empty when you run this Actor from an integration (see step 5).
-
Pick a mode. Append for logs, replace for snapshots, upsert for lists that change (set Key fields, e.g.
url). -
Automate it. On the Actor that produces your data, open the Integrations tab, add this Actor, and it will run after every successful run. The Dataset field is filled in with
{{resource.defaultDatasetId}}, which means "the dataset of the run that just finished"; leave it that way. Or create a schedule with a fixed dataset.
Input
| Field | What it does |
|---|---|
spreadsheetUrl | The sheet's URL (or bare id). A #gid= in the link selects that tab. |
sheetName | Tab to write. Empty = first tab. Created if missing (see createSheet). |
mode | append (default), replace, or upsert. |
keyFields | Column(s) that identify a row, for upsert and deduplication. Nested: seller.id. |
deleteMissing | Upsert only: remove sheet rows whose key is absent from the data. Skipped when the source is empty or cut off by limit. |
dedupe | Remove duplicates before writing. Default on for upsert, off otherwise. |
datasetId | Dataset to send. Filled in automatically when triggered by an integration or webhook. |
rows | Rows to write instead of a dataset (JSON array). Good for testing and API calls. |
columns | Write only these data columns. Their order applies to new tabs and replace mode; an existing tab keeps its layout. |
limit, offset | Read at most limit items, skipping offset. |
flattenDepth | Nested objects become dotted columns to this depth (default 2); deeper values and arrays become JSON text. |
maxColumns | Safety cap on the number of columns (default 200). |
parseValues | Off: values are written as-is. On: Google interprets them as typed input (dates, formulas). |
createSheet | Create the named tab when missing (default on). |
serviceAccountJson | Your own service account key (stored encrypted). |
Output
The Google Sheet is the real output. The Actor's own dataset holds one summary row per run so you can monitor syncs:
{"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiM.../edit#gid=0","spreadsheetTitle": "Competitor prices","sheetName": "Sheet1","mode": "upsert","rowsRead": 1240,"rowsDeduplicated": 12,"rowsWritten": 1228,"rowsAppended": 31,"rowsUpdated": 1197,"rowsDeleted": 0,"rowsSkipped": 0,"columns": 14,"columnsAdded": ["rating"],"columnsDropped": [],"apiReads": 2,"apiWrites": 3,"apiRetries": 0,"stoppedEarly": false,"warnings": [],"durationSeconds": 6.4,"finishedAt": "2026-09-27T20:11:04.000Z"}
Pricing
Pay per event: a small fee per sync plus a fraction of a cent per row written or updated. Rows that are not written (for example when your maximum cost per run is reached) are never charged; the run summary and status say when it stopped early. Exact prices are on this page's pricing box.
Tips
- Upsert keys: use a column that never changes for the same item, such as
url,id, orsku. Two key fields work too (["store", "sku"]). - Dates: keep
parseValuesoff if you want text to stay text. Turn it on to let Google turn ISO dates into real dates. - Big sheets: a Google Sheet holds at most 10 million cells. The Actor checks before writing and stops with a clear message instead of a Google error. Use
replacemode or a new sheet for rolling snapshots. - Column order: existing columns keep their positions; new fields are added on the right.
columnsfixes the order for a new tab or in replace mode. - Replace mode rewrites the tab in place from row 1 and trims the grid to the data, so formulas or charts in other tabs that point at it keep pointing at the same rows. Anything you typed into that tab is gone after a replace; keep notes in another tab.
- One sync at a time per tab: two runs writing the same tab at the same moment (a webhook that fired twice, a schedule overlapping a manual run) can duplicate rows in upsert mode. Let one finish first.
- Big syncs: over about 100,000 rows, give the run 2 GB of memory in the run options.
- Heavy use: the shared service account has one Google write quota for everyone. If you sync many times a minute, bring your own service account (below) for a dedicated quota.
One Apify account per sheet
The first Apify account that syncs a sheet becomes its only writer through this Actor: the Actor leaves an invisible marker in the spreadsheet (Google "developer metadata" that only this Actor's Google project can read), and runs from any other Apify account are refused. So even if someone learns your sheet's link, they cannot write to it through this Actor. Sync your sheet once right after sharing it, so the link is yours. A copy of the sheet (File > Make a copy) starts unlinked. If you move to a new Apify account, either use a copy, or open an issue in the Issues tab with the sheet link and the link will be reset. The lock does not apply to runs that bring their own service account key, and it does not limit people you share the sheet with in Google.
Bring your own Google service account
- In Google Cloud Console, create a project and enable the Google Sheets API.
- IAM & Admin > Service Accounts > Create service account. No roles needed.
- Open it, Keys > Add key > Create new key > JSON. Download the file.
- Paste the file's contents into Your own Google service account key. Share your sheet with that account's
client_email.
Privacy
The Actor reads the spreadsheet's title, tab names and sizes, the link marker, and the tab it writes (the header row in append and replace mode, the whole tab in upsert mode). It keeps nothing after the run: no copy of your sheet, no credentials, no rows. Its run summary (in your own Apify account) stores the spreadsheet id, link and title, tab name, mode, counts, names of columns added or dropped, warnings and timing, never cell values. Run logs show sheet, tab and column names, never cell values. It uses the Google Sheets API scope only and cannot list or open anything in your Drive that you did not share with it. Full policy: Privacy Policy
FAQ
The run says "Share the sheet with ..." Google refused access. Open the sheet, click Share, add the email shown as an Editor, and run again.
Numbers came in as text. Numbers from the dataset stay numbers. Only text that looks like a number stays text unless parseValues is on.
The run says the dataset could not be opened (insufficient permissions). This Actor runs with limited permissions, so it can only read the dataset named in the Dataset field. In an integration, set that field to {{resource.defaultDatasetId}}.
Upsert keeps adding the same rows. With parseValues on, Google may reformat key values (leading zeros, dates), so they no longer match. Use plain ids as keys or turn parseValues off.
Can it write to Excel or CSV? Every Apify dataset already exports to CSV and Excel from the dataset page. This Actor is for live sheets that update on a schedule.
"already linked to a different Apify account". Another Apify account (or an organization account) synced this sheet first. Use a copy of the sheet (File > Make a copy, share the copy with the service account), paste your own service account key, or open an issue with the sheet link to have it reset.
Something else? Open an issue in the Issues tab.