Google Sheets Export - Dataset to Sheet avatar

Google Sheets Export - Dataset to Sheet

Pricing

from $0.25 / 1,000 row exports

Go to Apify Store
Google Sheets Export - Dataset to Sheet

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

BrightLedger

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Categories

Security

Limited permissions

Share

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)

  1. Open your Google Sheet. Click Extensions > Apps Script.
  2. Delete what is there and paste the script below.
  3. Change SECRET to a long phrase only you know.
  4. Click Deploy > New deployment, choose Web app, set Execute as: Me and Who has access: Anyone, then Deploy and allow access.
  5. 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)

  1. Create a service account in Google Cloud with the Google Sheets API enabled, and download a JSON key.
  2. Share your spreadsheet with the service account's email as Editor.
  3. 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);
}