Google Sheets Import & Export: Append & Dedupe
Pricing
from $0.03 / 1,000 row read or writtens
Google Sheets Import & Export: Append & Dedupe
Export any Apify dataset to Google Sheets or import a sheet to JSON/CSV. Append or replace, skip duplicates by a key column, map columns. Service account key (no expiring login), clear setup errors, public sheets without a key.
Pricing
from $0.03 / 1,000 row read or writtens
Rating
0.0
(0)
Developer
Ctrio Lab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
Google Sheets Import & Export
Send any Apify dataset to Google Sheets, or read a Google Sheet into a dataset. Built to not fail silently: your key, sheet access, tab, range and Google's limits are checked before anything is written, and every error tells you exactly what to fix.
- Export: append or replace rows in a tab from an Apify dataset (for example the results of another Actor run) or from inline JSON.
- Import: read a whole tab or an A1 range into the run's dataset (JSON, CSV, Excel, API).
- Skip duplicates on append by a key column (url, id, email...). Duplicates are not written and not charged.
- Column mapping: keep only some columns, set their order, rename them.
- No OAuth token that expires: you use a Google service account key, so scheduled runs keep working.
- Public sheets without any key (read only).
Why this Actor?
Most failed Google Sheets runs are setup problems: an expired Google login, a sheet that is not shared, a wrong ID, or a sheet that hits Google's cell limit. This Actor is designed around them:
| Situation | What this Actor does |
|---|---|
| Scheduled runs for months | Service account key: no browser login, nothing expires |
| The sheet is not shared | The error tells you which email to share the sheet with |
| Appending to a big sheet | Append only writes the new rows; existing rows and columns are not touched |
| The data would not fit | Checks Google's 10,000,000 cell limit before writing anything |
| A replace run fails half way | New data is written first, old rows are cleared after, so the sheet is never left empty |
| 429 "Quota exceeded" from Google | Waits and retries with exponential backoff (honours Retry-After) |
1,1 or =SUM() in scraped text | Values are written as-is by default (no formula injection) |
Set up in 3 minutes
A Google service account is a robot Google account that only has access to the sheets you share with it.
You need this for export and for private sheets. It is free.
- Open Google Cloud Console, create or pick a project, and enable the Google Sheets API: APIs & Services > Library > Google Sheets API > Enable.
- Go to IAM & Admin > Service Accounts > Create service account. A name is enough, no roles are needed. Open the new account > Keys > Add key > Create new key > JSON. A
.jsonfile downloads. - Open the JSON file in a text editor and paste all of it into the Service account JSON key field. Apify stores it encrypted.
- In Google Sheets click Share and add the
client_emailfrom the JSON file (looks likename@project.iam.gserviceaccount.com) as Editor (Viewer is enough for import). Untick "Notify people".
That's it. The same key works for every sheet you share with that email.
Your organization blocks service account keys? Some Google Workspace admins enforce the policy
iam.disableServiceAccountKeyCreation. Ask the admin, or use a project in a personal Google account and share the sheet with it.
Quick start
Export the results of another Actor run
Add this Actor as an integration to your scraper (Integrations > Connect Actor) and set Dataset to export to {{resource.defaultDatasetId}}. Every finished run then lands in your sheet.
{"mode": "export","spreadsheet": "https://docs.google.com/spreadsheets/d/<ID>/edit","serviceAccountKey": "{ ...contents of your JSON key... }","sheetName": "Leads","datasetId": "{{resource.defaultDatasetId}}","writeMode": "append","dedupeKey": "url"}
Import a sheet
{"mode": "import","spreadsheet": "https://docs.google.com/spreadsheets/d/<ID>/edit#gid=0","range": "A1:F500"}
Without a key this works when the sheet is shared as Anyone with the link can view. The #gid= in the URL picks the tab. With a key you can use Tab name and private sheets.
Call it from code
curl -X POST "https://api.apify.com/v2/acts/ctriolab~google-sheets-import-export/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"mode":"import","spreadsheet":"https://docs.google.com/spreadsheets/d/<ID>/edit#gid=0"}'
How the data is laid out
- Nested fields become columns with
/:{"address": {"city": "Paris"}, "tags": ["a","b"]}becomesaddress/city,tags/0,tags/1(same convention as other Apify tools). - Append: the sheet's header row stays as it is. Rows are matched to columns by header name. New fields are added as new columns at the right end (turn off Add new columns to skip them).
- Replace: writes header + rows from A1, then clears any old rows below and columns to the right.
- Inline rows can be objects
[{"name":"A"}]or arrays with a header row first[["name"],["A"]]. - Cells longer than 50,000 characters (Google's limit) are cut, and the run log says how many.
- Import returns one item per row with the header names as keys. Empty rows are skipped. Duplicate or empty header names become
Name_2,C.
The run summary (rows written, duplicates skipped, added columns, target range, a link to the tab) is saved as RUN_SUMMARY in the key-value store.
Pricing
Pay per event, you only pay for rows that are actually read or written:
| Event | Price |
|---|---|
| Actor start | $0.002 per run |
| Row read or written | $0.05 per 1,000 rows |
Example: a daily export of 2,000 rows costs $0.102 per run (Apify plan discounts lower the row price further). Duplicates skipped by Skip duplicates are free. If you set a maximum cost per run, the Actor writes only as many rows as fit and says so in the log, it never writes rows it cannot charge.
Limits
- Google allows about 60 write requests per minute per user. This Actor writes up to ~1.5 MB per request (thousands of rows) and retries on 429, so large exports are fine.
- A Google spreadsheet can hold 10,000,000 cells across all tabs (empty grid cells count too).
- Apify input is limited to 9 MB: put large data in a dataset rather than Inline rows.
- Append reads the existing tab once to find the last row, the header and the duplicate keys. Very large tabs (millions of cells) make this step slower.
Troubleshooting
| Message | Fix |
|---|---|
| "The service account has no access to this spreadsheet" | Share the sheet with the client_email shown in the message |
| "The Google Sheets API is not enabled" | Open the link in the message, click Enable, wait 1 to 2 minutes |
| "is an OAuth client file" / "contains an API key" | You pasted the wrong file. Create a service account JSON key (step 2) |
| "This file is an Excel (.xlsx) file" | In Sheets: File > Save as Google Sheets, use the new URL |
| "Could not read the spreadsheet as a public sheet" | Share as "Anyone with the link can view", or add a key |
| "Tab ... not found. Tabs in this spreadsheet: ..." | Use one of the listed names (import does not create tabs) |
| "Dataset ... not found" | Use the dataset ID (Storage > Datasets, or defaultDatasetId of a run), not the run ID |
Switching from lukaskrivka/google-sheets
Keep your input JSON. Change the Actor name to ctriolab/google-sheets-import-export and add one field, serviceAccountKey (3 minute setup). The Actor recognises the old format, translates it, and logs what it did.
Before (lukaskrivka/google-sheets, connected Google account):
{"mode": "append","spreadsheetId": "1yKx...Oz0w","range": "Leads","datasetId": "{{resource.defaultDatasetId}}","deduplicateByField": "url"}
After (this Actor, same input plus the key):
{"mode": "append","spreadsheetId": "1yKx...Oz0w","range": "Leads","datasetId": "{{resource.defaultDatasetId}}","deduplicateByField": "url","serviceAccountKey": "{ ...contents of your JSON key... }"}
Remember to share the sheet with the service account email. For publicSpreadsheet: true reads you do not need a key at all.
| Old field | What happens here |
|---|---|
mode: "append" / "replace" | Export with append / replace |
mode: "read" | Import: rows go to the dataset and to the OUTPUT record, as before |
mode: "modify", "load backup" | Not supported, the run stops with an explanation |
spreadsheetId, the spreadsheet picker | Used as the spreadsheet |
range (Sheet1, Sheet1!A1:D100, A1:D100) | Tab and range. Export always writes from A1 of the tab |
datasetId, rawData, limit, offset | Same meaning |
deduplicateByField | Same field is the key. Existing rows in the sheet are never changed or removed |
deduplicateByEquality | Rows equal in every column to an existing or earlier row are skipped |
columnsOrder | These columns first, then the rest alphabetically (as before). Arrays from rawData keep their order |
keepSheetColumnOrder | Existing header order is kept. Append always keeps it |
publicSpreadsheet | Read without a key. The tab in range is found by name |
Connected Google account (oAuthAccount..., tokensStore) | Replaced by serviceAccountKey. Without it the run stops at once and links the setup |
googleCredentials | Used if it is a service account key, otherwise treated as a Google account connection |
transformFunction | Ignored with a warning: this Actor does not run code from the input |
createBackup, backupStore | Ignored with a warning. Replace never leaves the sheet empty |
Measured by us: all 26 of our own runs with valid input succeeded (export append and replace, dedupe, import of private and public sheets, legacy input; 2026-10-23 to 2026-10-24). Inputs with a setup error stop within about 2 seconds with a message that names the fix.
FAQ
Is my key safe? The field is marked secret, so Apify stores it encrypted and hides it in the UI and API. The Actor only uses it to get a one-hour token from Google for the Sheets scope and never logs it. You can delete the key in Google Cloud at any time.
Can it write to a sheet I don't own? Only if the owner shares it with your service account email.
Does it support Excel files? Only real Google Sheets. Convert .xlsx with File > Save as Google Sheets.
Can I use it from AI agents (MCP)? Yes, like any Apify Actor, via the Apify MCP server.
Made by Ctrio Lab. Issues and feature requests: the Issues tab of this Actor.