Google Sheets Export - Dataset to Sheet
Pricing
from $0.25 / 1,000 row exports
Google Sheets Export - Dataset to Sheet
Send any Apify dataset into your own Google Sheet: append, replace, or add only new rows. Flattens nested data, never runs scraped text as formulas, and pays only for rows written.
Pricing
from $0.25 / 1,000 row exports
Rating
0.0
(0)
Developer
BrightLedger
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Security
Limited permissionsShare
Send any Apify dataset into your own Google Sheet: the output of a scraper, a lead list, a price tracker. Append, replace, or add only the rows that are new.
Why this one
- Reliable: small batches with clear error messages that tell you exactly what to fix.
- Three modes: append all rows, append only new rows (skip duplicates by a key such as
url), or replace the tab. - Clean columns: nested data is flattened (
socials.linkedin), lists become comma-separated text, new fields become new columns at the end without disturbing yours. - Safe: scraped text is never run as a spreadsheet formula.
- Fair pricing: you pay only for rows actually written. Skipped duplicates are free.
Setup option A: Apps Script (5 minutes, no Google Cloud account)
- Open your Google Sheet. Click Extensions > Apps Script.
- Delete what is there and paste the script below.
- Change
SECRETto a long phrase only you know. - Click Deploy > New deployment, choose Web app, set Execute as: Me and Who has access: Anyone, then Deploy and allow access.
- Copy the Web app URL into this Actor's Apps Script web app URL field, and the same phrase into secret phrase.
The script only writes to the sheet it lives in, and rejects any request without your secret.
Two things you may see while deploying:
- "Google hasn't verified this app." This is normal for any script you add to your own sheet: the "developer" it names is your own Google account. Click Advanced, then Go to (your project) (unsafe), then Allow.
- "Sorry, unable to open the file at this time." This is a known Google bug when you are signed into several Google accounts in one browser. Open an incognito window, sign in only with the account that owns the sheet, and deploy again.
Keep the web app URL and secret private: together they allow writing to that sheet.
Setup option B: service account (for Google Cloud users)
- Create a service account in Google Cloud with the Google Sheets API enabled, and download a JSON key.
- Share your spreadsheet with the service account's email as Editor.
- Paste the JSON key and the spreadsheet URL into the Actor input.
Run it after another Actor automatically
In your scraper's Integrations tab, add this Actor and set the Dataset field to {{resource.defaultDatasetId}}. Every finished run lands in your sheet, and Append only new rows keeps it free of duplicates.
Output
The rows go to your sheet. The run also saves a short summary: rows written, duplicates skipped, number of columns, and whether it stopped at your spending limit.
Limits
- Google allows 10 million cells per spreadsheet and 50,000 characters per cell; longer text is truncated.
- Very large exports (hundreds of thousands of rows) are faster with option B.
Receiver script (option A)
/*** BrightLedger Google Sheets Export - receiver script.* Paste this into your sheet: Extensions > Apps Script. Change SECRET, then Deploy > New deployment > Web app,* Execute as: Me, Who has access: Anyone. Copy the web app URL into the Actor input together with the same SECRET.* The script only writes to the spreadsheet it is attached to.*/const SECRET = 'CHANGE-ME-to-a-long-random-phrase';function doPost(e) {let req;try {req = JSON.parse(e.postData.contents);} catch (err) {return out({ ok: false, error: 'Request was not valid JSON' });}if (SECRET.indexOf('CHANGE-ME') === 0) return out({ ok: false, error: 'Set your own SECRET in the script first' });if (req.secret !== SECRET) return out({ ok: false, error: 'Secret does not match the one in the script' });const lock = LockService.getDocumentLock();lock.waitLock(30000);try {const ss = SpreadsheetApp.getActiveSpreadsheet();const sheet = ss.getSheetByName(req.sheet) || ss.insertSheet(req.sheet);if (req.action === 'read') {const lastRow = sheet.getLastRow();const lastCol = sheet.getLastColumn();if (lastRow === 0 || lastCol === 0) return out({ ok: true, header: [], keys: [] });const header = sheet.getRange(1, 1, 1, lastCol).getValues()[0].map(String);let keys = [];const keyCols = req.keyColumns || [];if (keyCols.length && lastRow > 1) {const idx = keyCols.map(function (k) { return header.indexOf(k); });if (idx.every(function (i) { return i >= 0; })) {const data = sheet.getRange(2, 1, lastRow - 1, lastCol).getValues();keys = data.map(function (r) { return idx.map(function (i) { return String(r[i]); }).join('␟'); });}}return out({ ok: true, header: header, keys: keys });}if (req.action === 'write') {if (req.clear) sheet.clear();const header = req.header || [];if (header.length) sheet.getRange(1, 1, 1, header.length).setValues([header]);const rows = req.rows || [];if (rows.length) {const start = Math.max(sheet.getLastRow(), 1) + 1;sheet.getRange(start, 1, rows.length, header.length).setValues(rows);}return out({ ok: true, written: rows.length });}return out({ ok: false, error: 'Unknown action' });} finally {lock.releaseLock();}}function out(obj) {return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);}