# Google Sheets Export & Import: Dataset to Sheet, No Failed Runs (`nerolabs/google-sheets-export`) Actor

Writes an Apify dataset, JSON or CSV to a Google Sheet (append, replace, update by key) or imports a sheet. Waits out Google's 429 limit, no duplicate rows, rolls over at the cell cap. Inputs: spreadsheet, mode, datasetId. $0.005 per job + $0.0002 per row. Agent-ready via x402 and MCP, no OAuth.

- **URL**: https://apify.com/nerolabs/google-sheets-export.md
- **Developed by:** [Adam Pearce](https://apify.com/nerolabs) (community)
- **Categories:** Developer tools, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.14 / 1,000 row written to the sheets

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Send your scraper results to Google Sheets, and never lose a run to Google's rate limit again.** This Actor writes any Apify dataset, JSON array or CSV/Excel file into a Google Sheet (append, replace, or update rows by a key column), or imports a sheet into a dataset. When Google says "too many requests" (HTTP 429), it waits and tries again instead of failing. When a spreadsheet fills up, it carries on in the next one. If anything genuinely cannot be written, it tells you exactly which rows and saves them. No silent half-imports.

### What does Google Sheets Export & Import do?

- **Export**: dataset (or JSON, or a CSV/Excel/JSON file link) to a Google Sheet tab.
- **Three write modes**: **append** below existing rows, **replace** the tab's contents, or **update by key** (match on `url`, `id`, `sku`, `email` ... change only the fields you send, add rows for new keys).
- **Import**: read a tab into an Apify dataset (first row = column names).
- **Plugs in after any scraper**: add it on your scraper's **Integrations** tab and the Dataset field is set to `{{resource.defaultDatasetId}}` for you, so every finished scrape lands in your sheet.
- **No Google login screen**: you share the sheet with one email address. That means it works from schedules, the API, n8n, Make, Zapier and AI agents, where an OAuth "Connect Google account" step cannot be clicked.

### Why use it instead of a free Google Sheets actor?

The most used free Google Sheets actor on the Store had **56% of its runs fail in the last 30 days** (49,232 of 88,370, Apify Store stats, 29-10-2026). Its own page lists the reasons: Google's limit of about 60 writes a minute ("the run fails and nothing is imported"), the spreadsheet cell cap ("errors out, nothing is imported"), and the Google account connection. This Actor is built around exactly those three:

| Problem | What happens here |
|---|---|
| Google's 429 rate limit | Waited out with exponential backoff (1 s, 2 s, 4 s ... 64 s, honouring Google's Retry-After), then retried. Every wait is counted in the run report. |
| Retries adding duplicate rows | Rows are written to exact row numbers, so a retried request rewrites the same cells. Nothing is ever written twice. |
| Few big writes instead of many small ones | Up to 10,000 rows (and about 1.5 MB) per request, so a 50,000-row export is 5 or 6 writes, nowhere near the limit. |
| Spreadsheet full (Google allows 10 million cells) | Rolls over into your next **overflow spreadsheet** with the same tab name and columns. Without one, it writes what fits and saves the rest as `UNWRITTEN_ROWS.csv` (not charged). |
| Run stopped or timed out | Progress is saved after every write. **Resurrect** the run and it continues from the next row. |
| Columns shuffled | Your sheet's column order is kept. New fields are added as new columns on the right, so formulas and charts keep pointing at the right columns. |

### How to use it

1. Open your Google Sheet, click **Share**, add **`nerolabs-sheets@nero-labs-sheets.iam.gserviceaccount.com`** as an **Editor**, untick "Notify people", and click Share.
2. Paste the sheet's link into **Spreadsheet**.
3. Pick a **Mode** and a source: a **Dataset**, **Raw data** (JSON) or a **File link**.
4. Run it. The report row shows rows written, new columns, rate limits waited out and the sheet link.

**To run it after every scrape:** open your scraper, go to **Integrations**, add "Google Sheets Export & Import", fill in Spreadsheet and Mode. Done.

The form comes prefilled with a Nero Labs demo sheet so you can see a run work straight away. That sheet is shared by everyone and replaced on every run (50 rows max), so use your own sheet for real data.

### Input example

```json
{
    "spreadsheet": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
    "sheetName": "Leads",
    "mode": "update",
    "keyColumn": "url",
    "datasetId": "{{resource.defaultDatasetId}}"
}
```

Other useful fields: **Columns to export** (pick and order fields), **Flatten nested fields** (on by default, `address/city` style, the same names as Apify's CSV export), **Let Google read values** (turn text dates and `=formulas` into real dates and formulas; off by default so text stays text), **Overflow spreadsheets**, **Rows per tab**, **Max rows**, **Skip first rows**.

### Output

Export modes write one report row per run (also saved as `OUTPUT`):

```json
{
    "status": "ok",
    "mode": "append",
    "message": "Wrote 3000 row(s) (3000 added, 0 updated) to Leads Sheet / Leads. Google rate-limited 4 request(s); each was waited out and retried, nothing was lost.",
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/.../edit",
    "tabs": ["Leads Sheet / Leads"],
    "rowsWritten": 3000,
    "rowsAppended": 3000,
    "rowsUpdated": 0,
    "rowsNotWritten": 0,
    "columnsAdded": ["phone"],
    "rateLimitHits": 4,
    "retries": 4,
    "secondsWaitedForGoogle": 21.4,
    "rolledOverTo": [],
    "warnings": []
}
```

Import mode puts the sheet's rows in the dataset, using the header row as field names. You can download the dataset as JSON, CSV, Excel or HTML.

### How much does it cost?

Pay per event, no subscription:

- **$0.005 per job** (once per run that writes or imports at least one row)
- **$0.0002 per row written** (appended, replaced or updated)
- **$0.0001 per row imported**

Examples: sending 1,000 rows after each daily scrape costs about **$0.21 a run, roughly $6 a month**. A one-off 50,000-row export costs about $10. Rate-limit retries, skipped rows, rows that did not fit and failed writes are **never charged**. Higher Apify plans get up to 30% off per row.

### Limits and honest notes

- Google allows **10 million cells per spreadsheet** (all tabs together) and 50,000 characters per cell. Longer cells are shortened and counted in the report.
- The service account cannot create new spreadsheets by itself (Google gives service accounts no Drive storage), which is why overflow uses spreadsheets you create and share.
- Update mode matches keys after trimming spaces, and never rewrites the key cell. It does not roll over into overflow spreadsheets.
- Replace mode overwrites rows from the top and then removes old rows left below, so the tab is never empty while it works. It keeps your existing columns.
- The Google connection is shared by all users of this Actor. Google's quota is shared too, which is exactly why every request is paced and retried rather than fired at once.
- Your data goes straight from Apify to Google's API. Nothing is stored by Nero Labs.

### FAQ

**Do I need to connect my Google account?** No. Sharing the sheet with the service account email is the whole setup, and you can remove it from the Share list at any time.

**Can an AI agent use this?** Yes. There is no interactive login, so agents can call it through Apify's MCP server or pay per call with x402. Give it a spreadsheet the account is shared on.

**What if a run fails anyway?** The report and the run's status say why in plain words (for example "Share the sheet with ... as an Editor"). Rows that were not written are listed and saved, never dropped quietly.

**Will it overwrite my formulas?** Append writes only into empty rows. Update writes only the fields you send. Replace rewrites the data rows.

If this saved you a failed export, a review on the Store page helps a lot. Questions or ideas: use the **Issues** tab.

# Actor input Schema

## `spreadsheet` (type: `string`):

The Google Sheet link (or its ID). Before the first run, open the sheet, click Share, add nerolabs-sheets@nero-labs-sheets.iam.gserviceaccount.com as an Editor and untick 'Notify people'. No Google login or OAuth step is needed, so it also works from the API, schedules, n8n, Make, Zapier and AI agents. The prefilled demo sheet belongs to Nero Labs and is cleared on every run; use your own sheet for real data.

## `mode` (type: `string`):

append adds rows after the last row with data. replace writes the new rows over the old ones, then removes old rows left below (the sheet is never left empty while it works). update matches rows on 'Key column' and changes only the fields your data has, adding rows for new keys. import reads the tab into this run's dataset (first row = column names). In every mode the sheet's own column order is kept and new fields are added as new columns on the right.

## `sheetName` (type: `string`):

Which tab to use. Created if it does not exist. Leave empty to use the tab in your link (#gid=...) or the first tab.

## `datasetId` (type: `string`):

The Apify dataset to send to the sheet, for example a scraper run's results. In an integration (run after another Actor) use {{resource.defaultDatasetId}}. Picked this way so the Actor runs with limited permissions: it can read the dataset you choose and nothing else on your account.

## `rawData` (type: `array`):

A JSON array of objects to send instead of a dataset. Each object becomes a row; its keys become columns.

## `fileUrl` (type: `string`):

A link to a CSV, TSV, Excel (.xlsx, first sheet), JSON or JSON Lines file to send instead of a dataset. Up to 100 MB. The download is not charged.

## `keyColumn` (type: `string`):

Update mode only. The field that identifies a row, such as url, id, sku or email. It must be a column in the sheet's header row (or in your data, for a new tab). Rows whose key matches are updated in place; only the fields present in your data are written, so other cells (formulas, notes, your own columns) are left alone. Matching ignores spaces at either end.

## `addNewRows` (type: `boolean`):

Update mode only. On: rows whose key is not in the sheet yet are added at the bottom (an upsert). Off: they are skipped and not charged.

## `columns` (type: `array`):

Optional. Only these fields, in this order (nested fields as parent/child, for example address/city). Leave empty to export every field. Columns already in the sheet keep their position either way.

## `flatten` (type: `boolean`):

On: nested objects become their own columns named parent/child (the same names Apify's CSV export uses). Lists of plain values go in one cell separated by '; '; lists of objects go in one cell as JSON. Off: every nested value is written as JSON text.

## `parseValues` (type: `boolean`):

Off (safe default): every value is written exactly as it is, so text starting with = or + stays text. On: values are entered as if typed into Google Sheets, so '2026-10-29' becomes a date and '=SUM(B2:B9)' becomes a formula.

## `importValues` (type: `string`):

Import mode only. Formatted gives the text you see (for example '£1,200.00'); unformatted gives the underlying value (1200).

## `limit` (type: `integer`):

Optional. Send (or import) at most this many rows. 0 or empty = all, up to 2,000,000 per run.

## `offset` (type: `integer`):

Optional. Skip this many rows of the source (or of the sheet, in import mode) first.

## `overflowSpreadsheets` (type: `array`):

Optional. Extra Google Sheets (links or IDs, each shared with the same service account email) to continue in when the main one reaches Google's limit of 10 million cells. Rows carry on in the same tab name with the same columns. Without one, rows that do not fit are listed, saved as UNWRITTEN_ROWS.csv and not charged; nothing is dropped silently. Not used in update mode.

## `rowsPerTab` (type: `integer`):

Optional, append and replace modes. Start a new tab ('Sheet1 2', 'Sheet1 3' ...) after this many data rows, to keep big tabs fast. 0 or empty = no limit.

## `maxCellsPerSpreadsheet` (type: `integer`):

Advanced. When to move on to the next overflow spreadsheet. Google's own limit is 10,000,000 cells across all tabs; set it lower to keep sheets lighter.

## `rowsPerRequest` (type: `integer`):

Advanced. How many rows go to Google in one write (each write is also kept under about 1.5 MB). Large batches mean fewer requests and fewer chances to hit Google's 60 writes a minute limit.

## Actor input object example

```json
{
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1gV5ZFKQsKgake9aKTTQRaPRwmA7yS4BZySrmzGqNVAg/edit",
  "mode": "append",
  "rawData": [
    {
      "company": "Northwind Traders",
      "city": "London",
      "website": "https://example.com/northwind",
      "rating": 4.6,
      "reviews": 128
    },
    {
      "company": "Contoso Bakery",
      "city": "Leeds",
      "website": "https://example.com/contoso",
      "rating": 4.8,
      "reviews": 342
    },
    {
      "company": "Fabrikam Plumbing",
      "city": "Bristol",
      "website": "https://example.com/fabrikam",
      "rating": 4.3,
      "reviews": 57
    },
    {
      "company": "Tailspin Toys",
      "city": "Manchester",
      "website": "https://example.com/tailspin",
      "rating": 4.9,
      "reviews": 811,
      "address": {
        "street": "1 High St",
        "postcode": "M1 1AA"
      }
    }
  ],
  "addNewRows": true,
  "flatten": true,
  "parseValues": false,
  "importValues": "formatted",
  "maxCellsPerSpreadsheet": 10000000,
  "rowsPerRequest": 10000
}
```

# Actor output Schema

## `results` (type: `string`):

Export modes: one report row per run. Import mode: the sheet's rows.

## `report` (type: `string`):

Status, rows written, rate limits waited out, tabs and warnings.

## `unwrittenRows` (type: `string`):

CSV of rows that did not fit, saved so nothing is lost.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "spreadsheet": "https://docs.google.com/spreadsheets/d/1gV5ZFKQsKgake9aKTTQRaPRwmA7yS4BZySrmzGqNVAg/edit",
    "rawData": [
        {
            "company": "Northwind Traders",
            "city": "London",
            "website": "https://example.com/northwind",
            "rating": 4.6,
            "reviews": 128
        },
        {
            "company": "Contoso Bakery",
            "city": "Leeds",
            "website": "https://example.com/contoso",
            "rating": 4.8,
            "reviews": 342
        },
        {
            "company": "Fabrikam Plumbing",
            "city": "Bristol",
            "website": "https://example.com/fabrikam",
            "rating": 4.3,
            "reviews": 57
        },
        {
            "company": "Tailspin Toys",
            "city": "Manchester",
            "website": "https://example.com/tailspin",
            "rating": 4.9,
            "reviews": 811,
            "address": {
                "street": "1 High St",
                "postcode": "M1 1AA"
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/google-sheets-export").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "spreadsheet": "https://docs.google.com/spreadsheets/d/1gV5ZFKQsKgake9aKTTQRaPRwmA7yS4BZySrmzGqNVAg/edit",
    "rawData": [
        {
            "company": "Northwind Traders",
            "city": "London",
            "website": "https://example.com/northwind",
            "rating": 4.6,
            "reviews": 128,
        },
        {
            "company": "Contoso Bakery",
            "city": "Leeds",
            "website": "https://example.com/contoso",
            "rating": 4.8,
            "reviews": 342,
        },
        {
            "company": "Fabrikam Plumbing",
            "city": "Bristol",
            "website": "https://example.com/fabrikam",
            "rating": 4.3,
            "reviews": 57,
        },
        {
            "company": "Tailspin Toys",
            "city": "Manchester",
            "website": "https://example.com/tailspin",
            "rating": 4.9,
            "reviews": 811,
            "address": {
                "street": "1 High St",
                "postcode": "M1 1AA",
            },
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/google-sheets-export").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1gV5ZFKQsKgake9aKTTQRaPRwmA7yS4BZySrmzGqNVAg/edit",
  "rawData": [
    {
      "company": "Northwind Traders",
      "city": "London",
      "website": "https://example.com/northwind",
      "rating": 4.6,
      "reviews": 128
    },
    {
      "company": "Contoso Bakery",
      "city": "Leeds",
      "website": "https://example.com/contoso",
      "rating": 4.8,
      "reviews": 342
    },
    {
      "company": "Fabrikam Plumbing",
      "city": "Bristol",
      "website": "https://example.com/fabrikam",
      "rating": 4.3,
      "reviews": 57
    },
    {
      "company": "Tailspin Toys",
      "city": "Manchester",
      "website": "https://example.com/tailspin",
      "rating": 4.9,
      "reviews": 811,
      "address": {
        "street": "1 High St",
        "postcode": "M1 1AA"
      }
    }
  ]
}' |
apify call nerolabs/google-sheets-export --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nerolabs/google-sheets-export"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/GpBhccFaIvFssstvt/builds/5ehNpAnAQpLVT1BYb/openapi.json
