# Google Sheets Import & Export: Append, No OAuth (`altronite/google-sheets-import-export`) Actor

Export an Apify dataset to Google Sheets (append under existing rows or replace a tab) or import a Google Sheet to JSON. Uses your own service account, so no OAuth sign-in; public sheets import with no key. Append keeps your column order. Rows that fail are never charged.

- **URL**: https://apify.com/altronite/google-sheets-import-export.md
- **Developed by:** [Altronite](https://apify.com/altronite) (community)
- **Categories:** Integrations, Automation
- **Stats:** 1 total users, 0 monthly users, 94.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.15 / 1,000 rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Google Sheets Import & Export: Append, No OAuth

Export an Apify dataset to Google Sheets, appending rows under your existing data or replacing a tab, or import a Google Sheet into a dataset. It uses the official Google Sheets API and your own Google service account, or a public sheet’s CSV export for imports without a key. No Google sign-in or OAuth consent screen: you share the sheet with your own service account's email, and you can revoke access any time by unsharing it.

**Failed rows are never charged.** Append keeps your header order and puts new fields in new columns on the right.

### Who it's for

- Scraper users who want results in a team sheet
- Growing logs (append) and rebuilt report tabs (replace)
- URL or keyword lists kept in a sheet and fed to other Actors

### Export a dataset to Google Sheets, or import a sheet

Three modes cover it: export with append, export with replace, and import.

**Export, append:** adds rows under the existing data. Your header order is kept, and new fields become new columns on the right.

```json
{
  "mode": "export",
  "writeMode": "append",
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1EXAMPLE-SHEET-ID-000000000000000/edit",
  "sheetName": "Leads",
  "datasetId": "EXAMPLEdatasetId00",
  "serviceAccountJson": "<paste the whole JSON key file>"
}
```

**Export, replace:** clears the tab, then writes a fresh header and the rows. `columns` picks the fields and their order.

```json
{
  "mode": "export",
  "writeMode": "replace",
  "spreadsheet": "1EXAMPLE_SHEET_ID_0000000000000000",
  "sheetName": "Daily report",
  "datasetId": "EXAMPLEdatasetId00",
  "columns": ["title", "price", "address.city"],
  "serviceAccountJson": "<paste the whole JSON key file>"
}
```

**Import:** reads a tab into this run's dataset. The first row is the header, and each row after it becomes one item.

```json
{
  "mode": "import",
  "spreadsheet": "1EXAMPLE_SHEET_ID_0000000000000000",
  "sheetName": "Keywords",
  "serviceAccountJson": "<paste the whole JSON key file>"
}
```

**Import a public sheet, no key needed:** if the sheet is shared as "Anyone with the link can view", leave
`serviceAccountJson` empty and the Actor reads the tab through the sheet's CSV export. Run it with an empty
input and it imports Google's public sample sheet, so you can see the output shape first. Writing (export)
always needs a service account.

```json
{
  "mode": "import",
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit",
  "maxRows": 10
}
```

**Send one scraper run's results to a sheet:** pick that run's dataset in the **Dataset** field, or pass its ID as `datasetId` through the API.

**Test the connection first:** a dry run plans the export without writing rows.

```json
{
  "mode": "export",
  "spreadsheet": "1EXAMPLE_SHEET_ID_0000000000000000",
  "sheetName": "Leads",
  "datasetId": "EXAMPLEdatasetId00",
  "serviceAccountJson": "<paste the whole JSON key file>",
  "dryRun": true,
  "maxRows": 1
}
```

### Use it from code or an AI agent

Run this actor with the Apify API, the Apify Python or JavaScript (JS) clients, or as a tool through the Apify MCP server. Pass the same input JSON through any of these interfaces.

Sheets import rows preserve your spreadsheet headers and do not add a `status` field. Check the run’s key-value store `OUTPUT.status` (`ok`, `limited` or `error`) instead. Export returns its summary in `OUTPUT`, not dataset rows.

Minimal input:

```json
{
  "mode": "import",
  "spreadsheet": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
}
```

### Setup: your own Google service account (one time)

A service account is a robot Google account you own. The Actor can reach only the sheets you share with it. Most Sheets failures happen here.

1. In the [Google Cloud Console](https://console.cloud.google.com/), create a project or pick an existing one.
2. Go to **APIs & Services → Library**, find **Google Sheets API** and click **Enable**. The Drive API isn't needed.
3. Go to **IAM & Admin → Service accounts → Create service account**. It doesn't need any roles.
4. Open the account, then go to **Keys → Add key → Create new key → JSON**. A `.json` file downloads.
5. In that file, copy the `client_email` value. It ends in `iam.gserviceaccount.com`.
6. **Share the sheet with this email.** In your spreadsheet, click **Share**, paste the email and choose **Editor**. Viewer is enough if you only import.
7. Paste the **entire** JSON file into **Service account key (JSON)** in this Actor's input.

On Google Workspace, an admin policy may block keys or outside sharing. If so, ask your admin.

### Input

| Field | What it does | Default |
|---|---|---|
| `mode` | `export` (dataset → sheet) or `import` (sheet → dataset) | `import` |
| `spreadsheet` | Sheet URL or ID | Google’s public sample sheet |
| `sheetName` | Tab to use. Created on export if it's missing | `Sheet1` |
| `serviceAccountJson` | Full JSON key (secret), required for export or private imports | empty (public import) |
| `datasetId` | Dataset to export | required for export |
| `writeMode` | `append`, or `replace` (clears the tab first) | `append` |
| `columns` | Export only these fields, in this order (`address.city` for nested fields) | all |
| `maxRows` | Stop after this many rows (0 = all) | `0` |
| `skipEmptyRows` | Import: skip blank rows | `true` |
| `dryRun` | Plan only: no rows written, no row charges | `false` |

Nested objects become dotted columns, lists become JSON text, and empties become blank cells. The `OUTPUT` record summarizes each run: `status` (`ok`, `limited` on import or export spending limits, or `error`), row counts, `spreadsheetUrl` and, on export, `columns`. Import `rowsImported` counts rows actually delivered, including when the SDK spending limit stops a batch.

### Pricing

This actor uses **pay-per-event** pricing: every run has a small start fee (`apify-actor-start`), charged once per run (per GB above 1 GB), plus a per-row charge (`row`) for rows written to the sheet (export) or added to the dataset (import). The rates are on the actor's Pricing tab. Dry runs incur no row charges; the start fee still applies. Failed rows are never charged. If a run stops part-way, row charges apply only to the rows that landed. If you set a maximum total charge on the run, export stops cleanly at that limit and `OUTPUT` says how many rows were written.

### Reliability

1. **Input is checked before Google is contacted.** That covers the sheet URL or ID, whether the key is a service-account JSON, and whether an export has a `datasetId`. Errors name the field and never repeat your input.
2. **A key that can't be read** stops the run with "The service-account key could not be read. Paste the whole JSON key file you downloaded."
3. **A wrong ID or missing Editor access** stops the run with a message that includes your service account's email and tells you to share the sheet with it as an Editor. If the Google Sheets API isn't enabled for the key's project, or the key was revoked, the message says that instead. No raw error traces.
4. **Rate limits and Google server errors** (429, 5xx) are retried with exponential backoff, one 1,000-row chunk at a time, so earlier chunks aren't resent. Before a retry, the sheet is checked: if the failed write had in fact landed, it isn't written again.
5. **If an export fails part-way,** `OUTPUT` records the reason and the count, for example "1000 of 2500 rows were written before this". There's no automatic resume: running again in append mode starts from the first row, so delete the partial rows first or use replace.
6. **A dataset that can't be opened** (wrong or expired ID) stops the run with a message saying so, instead of a stack trace. Pick the dataset with the **Dataset** field's picker, or pass its ID through the API.
7. **A tab name that doesn't exist on import** stops the run and lists the sheet's tabs. Nothing is imported or charged. Left at the default `Sheet1`, the first tab is used.
8. **Close to the run's timeout** the export stops starting new chunks and ends with `status: limited` and a count of rows written, instead of timing out.
9. **A dry run** opens the sheet and reports the planned columns and row count without writing rows. On export it still creates a missing tab. It doesn't prove Editor access, because reading needs only Viewer.

For a quick connection test, set `dryRun: true` and `maxRows: 1`.

### Use cases

- **Lead-gen team:** A marketing agency scrapes business listings nightly and appends each run's dataset to a "Leads" tab so the sales team can review new rows without re-downloading a file.
- **Daily price reports:** An e-commerce scraper produces a dataset per run, and a scheduled task replaces a "Daily report" tab each morning with only `title`, `price`, and `address.city` in a fixed column order.
- **Shared keyword lists:** A content team keeps target keywords in a sheet and imports that tab into a dataset to feed the URLs into another actor that does the actual scraping.
- **Public reference data:** A developer pulls a sheet that's shared as "Anyone with the link can view" into a dataset without setting up a service account, using `maxRows` to test the output shape first.
- **Growing log file:** A long-running scraper appends each batch of results to the same tab so past entries are kept, while new fields from later runs appear as extra columns on the right.
- **Connection testing:** Before wiring the actor into a schedule, an operator runs it with `dryRun: true` and `maxRows: 1` to confirm the sheet URL and key are correct without writing rows.

### FAQ

**How do I export an Apify dataset to Google Sheets?**
Set `mode` to `export`, give the sheet URL or ID, a `sheetName`, the `datasetId` and your service account key, share the sheet with the service account's email as Editor, then choose `append` or `replace`.

**Can I import a Google Sheet without a Google account key?**
Yes, if the sheet is shared as "Anyone with the link can view". Leave `serviceAccountJson` empty and the actor reads the tab through its CSV export. Private sheets need the service account.

**Why do I need a service account?**
Only to write, or to read a private sheet. Google needs an identity to write. A service account means no password and no broad OAuth grant. It sees only the sheets you share, and you can revoke access by unsharing or deleting the key.

**Is my key stored?**
It's a secret input, so Apify stores it encrypted with the run input. The Actor never logs the key, never writes it to the dataset, `OUTPUT` or any other storage, and never echoes input in errors. The log shows the service account's email, not the key. Data goes from Apify straight to Google, and the publisher keeps no copy.

**Will it reorder my columns?**
Not when you append with `columns` left empty: existing columns stay where they are, and new fields go on the right. If you set `columns`, that list becomes the header exactly, so on a tab that already has data, match its order or use replace. Keep the header row free of blank cells.

**Can I schedule it?**
Yes, with Apify Schedules or tasks. For export, a fixed `datasetId` with append re-adds the same rows on every run, so use replace, or pass each new scraper run's dataset ID.

**What does import return?**
Every value comes back as text, as the sheet displays it. Blank headers become `column_2`, `column_3` and so on. Repeated headers are kept apart as `name`, `name_2`, `name_3`, so no column is lost.

**Are formulas evaluated on export?**
No, they're written as plain text.

### Limits

- One dataset and one tab per run. There's no upsert or deduplication.
- Google caps a spreadsheet at 10 million cells and a cell at 50,000 characters. Longer values are cut and marked `…[truncated]`.
- Google applies per-minute request quotas to each Cloud project. Export makes roughly one write request per 1,000 rows and backs off on 429.
- Rows are held in memory during a run (2 GB max).
- Replace with an empty dataset leaves the tab untouched.

### Related tools

- [Google Trends Scraper](https://apify.com/altronite/google-trends): track search interest for your keywords.
- [Shopify Store Monitor](https://apify.com/altronite/shopify-store-monitor): watch Shopify stores for new arrivals, price drops and sold-out products.
- [YouTube Transcript Scraper](https://apify.com/altronite/youtube-transcripts): get video transcripts as text, SRT or VTT.

### Support

Open an issue on the **Issues** tab with your input (**remove the key first**) and the error message.

# Actor input Schema

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

Export copies a dataset into a sheet. Import reads a sheet into this run's dataset.

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

Paste the sheet's URL from your browser, or just its ID. The prefilled value is Google's own public 'Class Data' sample sheet, so you can run this Actor with no setup to see how it works.

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

The tab (worksheet) to use. On export it is created if it doesn't exist. On import, a tab name that doesn't exist stops the run with the list of the sheet's tabs (nothing is imported or charged); left at the default Sheet1, the first tab is used.

## `serviceAccountJson` (type: `string`):

Paste the full JSON key of a Google service account, then share your spreadsheet with that account's email as Editor (Viewer for import). Stored encrypted; never logged or saved to any dataset. See the README for a 2-minute setup. Export always needs a key. Import works without one if the sheet is shared as 'Anyone with the link can view'. The key file you download from Google Cloud contains "type": "service_account", a "client_email" and a "private_key"; paste the whole file.

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

The dataset to copy into the sheet: pick one, or paste a dataset ID or name from another run.

## `writeMode` (type: `string`):

Append adds rows under existing data and keeps your columns in place. Replace clears the tab first.

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

Export only these fields, in this order. Nested fields use dots, e.g. address.city. Leave empty for all fields.

## `maxRows` (type: `integer`):

Stop after this many rows (0 = no limit). Prefilled small so a first, key-less run finishes in seconds.

## `skipEmptyRows` (type: `boolean`):

Ignore completely blank rows when importing.

## `dryRun` (type: `boolean`):

Check access and show what would happen, without writing anything or being charged.

## Actor input object example

```json
{
  "mode": "import",
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit",
  "sheetName": "Sheet1",
  "writeMode": "append",
  "maxRows": 10,
  "skipEmptyRows": true,
  "dryRun": false
}
```

# Actor output Schema

## `summary` (type: `string`):

status (ok or error), mode, spreadsheetUrl, sheetName, rowsWritten or rowsImported, columns, dryRun, error (plain-words fix-it message when status is error) and finishedAt.

## `importedRows` (type: `string`):

Import mode only: one item per non-empty sheet row. Keys are the sheet's header cells; a blank header becomes column_N. Export mode leaves this empty.

# 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 = {
    "mode": "import",
    "spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit",
    "maxRows": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("altronite/google-sheets-import-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 = {
    "mode": "import",
    "spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit",
    "maxRows": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("altronite/google-sheets-import-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 '{
  "mode": "import",
  "spreadsheet": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit",
  "maxRows": 10
}' |
apify call altronite/google-sheets-import-export --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,altronite/google-sheets-import-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/lH1g2153zGDdwBbha/builds/4zcPi7RLPezeA17Kg/openapi.json
