Dataset to Google Sheets — No OAuth
Pricing
from $5.00 / 1,000 sheet updateds
Dataset to Google Sheets — No OAuth
Append or replace rows in your Google Sheet from any Apify dataset. Share the sheet once with the Actor's e-mail; no Google login, no tokens that expire.
Pricing
from $5.00 / 1,000 sheet updateds
Rating
0.0
(0)
Developer
Duffy Radmilović
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
16 hours ago
Last modified
Categories
Share
Send any Apify dataset into your Google Sheet: append new rows, skip duplicates, or replace the tab.
There's no Google login and no OAuth token. Share your sheet once with the Actor's e-mail and it keeps
working. No token to expire, no invalid_grant, no reconnecting accounts.
Why this exists
The usual way to get scraped data into Google Sheets is to connect your Google account with OAuth. Those tokens expire, get revoked, or get tied to the wrong account, and scheduled runs start failing.
This Actor uses a Google service account instead. You give it access the same way you'd give a colleague access: Share → add an e-mail → Editor. It can only touch spreadsheets you shared with it.
Setup (one minute, once per spreadsheet)
-
Create or open a Google Sheet.
-
Click Share and add this e-mail as Editor:
sheets-bot@helena-sheets.iam.gserviceaccount.comIf Google says the address has no Google account, share anyway. That's normal for service accounts.
-
Copy the sheet link into the Google Sheet link field and pick a dataset.
If you forget step 2, the run tells you exactly that, with the e-mail to add. It charges nothing beyond Apify's minimal start fee.
Use it after another Actor (the main use case)
Run a scraper on a schedule and have every run land in the same sheet automatically:
-
Open the scraper (or its task) in Apify Console → Integrations → connect an Actor → choose Dataset to Google Sheets — No OAuth.
-
In the integration input set:
{"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit","sheetName": "Results","mode": "append","dedupeBy": "url","datasetId": "{{resource.defaultDatasetId}}"} -
Done. Each successful scraper run appends only rows whose
urlisn't in the tab yet.
Important: the dataset has to arrive through the datasetId field. Apify only grants this Actor read
access to datasets passed that way.
Tested end to end: a scraper run followed by this Actor wrote 57 rows. The next identical run wrote 0 and skipped 57 duplicates.
Input
| field | default | meaning |
|---|---|---|
spreadsheetUrl | (required) | Link of the Google Sheet, or its ID. |
sheetName | Sheet1 | Tab to write into. Created if missing. |
datasetId | — | Dataset to copy. In integrations use {{resource.defaultDatasetId}}. |
rows | — | Alternative to a dataset: a JSON array of objects. |
mode | append | append adds rows below existing ones. replace clears the tab first. |
dedupeBy | — | Field name. In append mode, rows whose value is already in that column are skipped. |
columns | all | Which fields to write, in this order. Nested fields use dots: price.amount. |
maxRows | 0 | Safety brake. 0 = no limit. |
How data is laid out
- Nested objects are flattened:
{"price": {"amount": 7}}→ columnprice.amount. - Lists of simple values become one cell:
["a","b"]→a, b. Lists of objects become a JSON string. - The header row only ever grows. Existing columns keep their position; new fields get new columns on the right. Your formulas and formatting to the left stay where they are.
- Values are written as they are (
RAW). Numbers stay numbers and text stays text. A product code like00123isn't turned into123, and a date-like string isn't reinterpreted.
Output
One row per run in the default dataset:
{"status": "OK","spreadsheetId": "17gw...nxU","sheetName": "Shopify","mode": "append","rowsIn": 57,"rowsWritten": 0,"rowsSkippedDuplicate": 57,"columns": ["shopDomain", "productId", "variantId", "title", "price"],"sheetUrl": "https://docs.google.com/spreadsheets/d/17gw...nxU/edit#gid=...","message": "Wrote 0 rows to \"Shopify\", skipped 57 duplicates.","finishedAt": "2026-09-24T19:16:10.000Z"}
Problems come back as a row with a clear status, not just a log line:
| status | meaning |
|---|---|
NOT_SHARED | The sheet isn't shared with the Actor's e-mail. The message says which e-mail to add. |
SPREADSHEET_NOT_FOUND | The link doesn't point to an existing sheet. |
DATASET_NOT_FOUND | The dataset doesn't exist. |
DATASET_NOT_ACCESSIBLE | The dataset wasn't passed through the datasetId field (see above). |
EMPTY_DATASET | Nothing to write. Nothing charged. |
BAD_INPUT | Missing or invalid input. The message says which field. |
TOO_LARGE | Writing would pass Google's 10 million cells per spreadsheet. Checked before writing. |
RATE_LIMITED | Google kept refusing after several retries with back-off. |
CHARGE_LIMIT_REACHED | Your maximum cost per run is too low to write anything. |
Pricing
Pay per event:
| event | price |
|---|---|
| Sheet updated (once per successful run) | $0.005 |
| Rows written, per started 1,000 rows | $0.02 |
- 500 new rows a day → $0.025 a day, about $0.75 a month.
- One-off export of 12,000 rows → 12 × $0.02 + $0.005 = $0.245.
- A scheduled run with no new rows (all duplicates) still costs $0.005, because the sheet was checked.
- Failed runs (not shared, bad input, empty dataset) are not charged.
If you set a maximum cost per run, the Actor writes only as many rows as that limit covers, then says so in the result.
Limitations — please read
- It can't create spreadsheets. Service accounts have no Drive storage of their own, so the Actor only writes into a sheet you created and shared.
- Google's limit is 10 million cells per spreadsheet, counting every tab. For very large exports use a
new spreadsheet or pick fewer
columns. - One cell holds at most 50,000 characters. Longer values are cut off with
…. - Google allows about 60 write requests per minute for this service account. The Actor writes up to 5,000 rows per request and retries with back-off, so normal use never gets close. Very heavy parallel use may be slowed down.
dedupeBychecks one column. It compares values as text.replaceclears values, not formatting.
Privacy
- The Actor can open only spreadsheets you explicitly shared with its e-mail. Share a sheet made for this purpose, not your whole Drive.
- It reads the spreadsheet's tab names and sizes, plus the header row and the
dedupeBycolumn of the tab it writes to. It reads no other cell contents. - Remove access at any time: Share → remove the e-mail.
- The Actor stores no copy of your data outside your Apify run.
FAQ
Why not just use Apify's Google Drive integration? That uploads a file (CSV/JSON) into a Drive folder. This Actor writes rows into a live Google Sheet, appends, and skips duplicates.
Can I write several scrapers into one spreadsheet?
Yes, use a different sheetName for each.
Does it work with shared drives / Google Workspace?
Yes, if your organisation allows sharing with outside e-mails. Some companies block it. Then the
run returns NOT_SHARED.
What if Google is down or slow? Requests are retried with exponential back-off. Rows already written stay written and are reported in the result.