# Google Sheets Import Export (`axiomworks/google-sheets-import-export`) Actor

Read rows from a Google Sheets tab or A1 range into an Apify dataset, or export any Actor's dataset to a sheet as an integration step: append, replace or append-only-new-rows (dedupe on key columns). Public sheets read without credentials; private sheets and writes need a service-account key.

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

## Pricing

from $0.35 / 1,000 results

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

## Google Sheets Import Export

**Returns:** one item per read or written row with selected column fields, `id`, `sourceUrl`, `_spreadsheetId`, `_sheetName`, `_sheetGid`, `_rowNumber`. Write previews add `_dryRun` and `_writeError`.
**Input:** `{"mode": "read", "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0", "maxItems": 20}`
**Best for:** Google Sheets API reads into datasets, syncing scraper output to a sheet, paging through large tabs.

```json
{
    "Student Name": "Alexandra",
    "Major": "English",
    "_sheetName": "Class Data",
    "_rowNumber": 2,
    "id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms:0:2",
    "sourceUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0"
}
```

For agents: set `mode` explicitly. Check `_dryRun`, `_writeError`, and `OUTPUT.rowsWritten` before confirming writes. Empty reads include corrective guidance.

### What does Google Sheets Import Export do?

**Google Sheets Import Export** moves rows between Google Sheets and Apify in both directions.

- **Import (read mode):** it reads one tab of a spreadsheet, or an A1 range of it, and saves **one dataset item per sheet row**. The header cells become the field names. Numbers and true/false cells come out as numbers and booleans, not text.
- **Export (write modes):** it takes the items of an Apify dataset, or a JSON array you paste in, and **appends** them to a tab, **replaces** the tab's contents, or **appends only rows that aren't there yet** (dedupe on key columns you choose).

Sheets shared as "Anyone with the link" can be read with no credentials. Private sheets and all writes go through the official Google Sheets API with a **service-account key that you supply**. There is no OAuth popup and no browser login, so the Actor runs well on a schedule, from the API, or as the last step of another Actor's workflow.

To try it, open the Input tab and click Start. The form is prefilled with Google's public example spreadsheet (a list of students), so you get 20 rows back in a few seconds.

This Actor is an independent tool. It is not made, sponsored or endorsed by Google.

### What data can you get from Google Sheets?

In read mode every dataset item holds **all the columns of one sheet row** plus a few fields that describe where the row came from. In write modes every item holds the cell values **exactly as they were sent to the sheet** (after flattening and formula escaping) plus the same location fields.

| Field | Type | Description |
| --- | --- | --- |
| *(one field per header cell)* | text, number or boolean | The row's cell values, keyed by the header text, e.g. `Student Name`, `Major`. A blank header cell becomes `column_C` style names. |
| `id` | text | Stable record ID: `spreadsheetId:tabGid:rowNumber`, e.g. `1BxiMVs0…upms:0:2`. If public tab discovery is unavailable, a supplied tab name replaces the gid. |
| `sourceUrl` | link | Link to the tab, e.g. `https://docs.google.com/spreadsheets/d/<id>/edit#gid=0`. Without a discovered gid, it is a query link to the named tab. |
| `_spreadsheetId` | text | Google spreadsheet ID. |
| `_sheetName` | text | Tab name from public tab discovery or your input; `gid:N` when neither provides a name. |
| `_sheetGid` | number | Numeric tab ID (gid), when known. |
| `_rowNumber` | number | 1-based row number in the tab. In write modes, the row the item was written to. |
| `_dryRun` | boolean | Write modes only: `true` when the row was planned but not written. |
| `_writeError` | text | Write modes only: why nothing was written (the row is then a dry-run preview). `null` otherwise. |

Write modes also save a run summary as the `OUTPUT` record in the key-value store (`mode`, `rowsRead`, `rowsWritten`, `rowsSkippedAsDuplicate`, `chargedEvents`, `updatedRange`, `dryRun`, `writeError`).

Download results as JSON or CSV from Storage or the API.

### How to use Google Sheets Import Export

#### Read a public Google Sheet

1. In Google Sheets, click **Share** and set General access to **Anyone with the link – Viewer**.
2. Copy the sheet's URL from the browser. Keep the `#gid=…` part if you want a specific tab.
3. Paste it into **Spreadsheet URL or ID**, keep **Mode** on `read` and click **Start**.
4. Open the Output tab to see one row per item, or download the dataset.

#### Read a private Google Sheet or write to any sheet

1. In the Google Cloud console, create a project (or use an existing one) and **enable the Google Sheets API**.
2. Create a **service account** and add a **JSON key**. Google downloads a `.json` file.
3. Open the file and copy its `client_email` value (it looks like `name@project.iam.gserviceaccount.com`).
4. In Google Sheets, click **Share** and add that email: **Viewer** to read, **Editor** to write.
5. Paste the **whole JSON file** into the **Service-account JSON key** field. The field is stored as an Apify secret and is never logged or written to the output.
6. Choose a mode, fill in `datasetId` or `rawData` for write modes, and click **Start**.

Write access is checked first. Default failures return a preview; see `onWriteError`.

#### Export another Actor's results automatically

Add this Actor as an integration on the other Actor (Integrations tab → "Run Actor") with an input like this:

```json
{
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/<your sheet id>/edit",
    "serviceAccountKey": "<your service-account JSON key>",
    "mode": "append-dedupe",
    "dedupeFields": ["url"],
    "datasetId": "{{resource.defaultDatasetId}}"
}
```

Omitted `datasetId` uses the triggering dataset from `resource.defaultDatasetId` or `payload.resource.defaultDatasetId`. Omitted mode infers append from source rows, or append-dedupe with `dedupeFields`.

A source with 0 items is not an error: the run ends successfully with the message "Source dataset had 0 items; nothing written. Choose a populated datasetId or provide a non-empty rawData array.", `rowsWritten: 0` in `OUTPUT`, and no charge.

### Input

All fields are on the Input tab. Only **Spreadsheet URL or ID** is required.

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `spreadsheetUrl` | text | – (required) | Sheet URL or bare spreadsheet ID. Account-specific `/u/0/d/` links, Google `open?id=` links and links without `https://` are accepted. A `#gid=` or `?gid=` selects the tab. |
| `mode` | `read`, `append`, `replace`, `append-dedupe` | see note | Read rows into the dataset, or write rows to the sheet. When omitted: `append` (or `append-dedupe` with `dedupeFields`) if `datasetId` or `rawData` is given, otherwise `read`. |
| `excludeColumns` | list | – | Drop these columns, in all modes. Applied after `columns`. |
| `sheetName` | text | tab from URL, else first tab | Tab name. Overrides the gid. Write modes create the tab when it doesn't exist. |
| `range` | text | whole tab | A1 range to read, e.g. `A1:C10` or `Class Data!A1:C10`. |
| `maxItems` | integer | 1000 (read), 0 (write) | Maximum rows to read or to take from the source. `0` means no limit. |
| `offset` | integer | 0 | Read mode: data rows to skip after the header, before `maxItems` applies. |
| `columns` | array | all | Keep only these columns, in this order (read: header names; write modes: item keys). |
| `headerRow` | integer | 1 | Row that holds the column names when reading. |
| `serviceAccountKey` | secret text | – | Full service-account JSON key. Needed for private sheets and every write. |
| `datasetId` | text | – | Apify dataset to export (write modes). |
| `rawData` | JSON array | – | Objects to write instead of a dataset. |
| `dedupeFields` | list | – | Key columns for `append-dedupe`. |
| `columnsOrder` | list | first-seen order | Header order for a tab that has no header yet. |
| `flattenNested` | boolean | `true` | Nested objects become `a/b` columns and arrays `a/0`, `a/1`. |
| `escapeFormulas` | boolean | `true` | Text starting with `=`, `+`, `-` or `@` gets a leading apostrophe. |
| `valueInputOption` | `USER_ENTERED`, `RAW` | `USER_ENTERED` | Let Google parse values as if typed, or store them exactly as sent. |
| `dryRun` | boolean | `false` | Plan the write and output the rows without changing the sheet. |
| `onWriteError` | `preview`, `fail` | `preview` | When the sheet can't be written: output a dry-run preview, or fail the run. |

Read example:

```json
{
    "mode": "read",
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0",
    "maxItems": 20
}
```

Write example (append only new URLs):

```json
{
    "mode": "append-dedupe",
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit",
    "sheetName": "leads",
    "serviceAccountKey": "<contents of your key file>",
    "rawData": [
        { "url": "https://a.example", "title": "A" },
        { "url": "https://b.example", "title": "B" }
    ],
    "dedupeFields": ["url"]
}
```

Invalid input stops the run right away with a clear message, for example a negative `maxItems`, an unknown `mode`, a URL that isn't a Google Sheets link in read mode, or a write mode with neither `datasetId` nor `rawData`.

### Output

A row from the public example sheet, read with the prefilled input (real output from a local run):

```json
{
    "Student Name": "Alexandra",
    "Gender": "Female",
    "Class Level": "4. Senior",
    "Home State": "CA",
    "Major": "English",
    "Extracurricular Activity": "Drama Club",
    "_spreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
    "_sheetName": "Class Data",
    "_sheetGid": 0,
    "_rowNumber": 2,
    "id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms:0:2",
    "sourceUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0"
}
```

A `replace` run with `rawData` `[{"name": "x", "nested": {"a": 1}, "tags": ["t1", "t2"], "f": "=1+1"}]` and no usable key (real output from a local run). It shows flattening (`nested/a`, `tags/0`), formula escaping (`'=1+1`) and the dry-run preview:

```json
{
    "name": "x",
    "nested/a": 1,
    "tags/0": "t1",
    "tags/1": "t2",
    "f": "'=1+1",
    "_spreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
    "_sheetName": "golden-replace",
    "_sheetGid": null,
    "_rowNumber": 2,
    "_dryRun": true,
    "_writeError": "serviceAccountKey is not valid JSON. Paste the full contents of the service-account JSON key file.",
    "id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms:golden-replace:2",
    "sourceUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit"
}
```

When the write succeeds, `_dryRun` is `false`, `_sheetGid` holds the tab's gid, `_writeError` is `null`, `rowsWritten` in the `OUTPUT` record counts the rows written and `updatedRange` shows the last range Google reported, e.g. `'golden-replace'!A2:E2`.

The key-value store also holds `OUTPUT` (the run summary).

Limited reads set `OUTPUT.truncated: true`. Raise `maxItems` or use 0 for all rows.

### How much does it cost to use Google Sheets Import Export?

The Actor uses pay-per-event pricing. You pay one **result** event per row read into the dataset in read mode, or written to the sheet in write modes. Dry runs, preview runs and rows skipped as duplicates are free. See the **Pricing** tab for the current price. Runs use three HTTP requests per public tab, no browser. The JSON request returns only column types; CSV retains the full cell values. Explicit tab reads overlap optional tab discovery, with public requests paced at one per second. Network retries have a bounded deadline. Google's own Sheets API quota is free for normal use.

### Use with the API

You can start the Actor from any language through the Apify API, and connect it to Make, Zapier, n8n, Slack, webhooks and other Actors from the Integrations tab.

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("axiomworks/google-sheets-import-export").call(run_input={
    "mode": "read",
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0",
    "maxItems": 20,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["_rowNumber"], row["Student Name"])
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('axiomworks/google-sheets-import-export').call({
    mode: 'append',
    spreadsheetUrl: 'https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit',
    sheetName: 'results',
    serviceAccountKey: process.env.GOOGLE_SA_KEY,
    datasetId: '<dataset id to export>',
});
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT');
process.stdout.write(JSON.stringify(summary.value));
```

#### cURL

```bash
curl -X POST "https://api.apify.com/v2/acts/axiomworks~google-sheets-import-export/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "read", "spreadsheetUrl": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms", "sheetName": "Class Data", "range": "A1:C10"}'
```

### Use with AI agents (MCP)

AI agents can call this Actor through the Apify MCP server. Add it to Claude Desktop, Cursor, VS Code or any MCP client:

```json
{
    "mcpServers": {
        "google-sheets": {
            "url": "https://mcp.apify.com?tools=axiomworks/google-sheets-import-export"
        }
    }
}
```

The server asks you to sign in with your Apify account the first time. Example prompts:

- "Read the first 50 rows of https://docs.google.com/spreadsheets/d/…/edit#gid=0 and tell me which majors appear most often."
- "Read the tab 'Q3 leads' of my sheet and list the rows where Status is empty."
- "Append these five companies to the 'prospects' tab, skipping any whose website is already in the sheet."
- "Do a dry run of replacing the 'export' tab with dataset aBcD1234 and show me the first three planned rows."

For write prompts, the agent needs your service-account key. Store it once in an Apify task and have the agent run the task, rather than pasting the key into a chat.

#### Use it from an AI agent

Use Apify's hosted MCP server at `https://mcp.apify.com` with OAuth and tool `axiomworks/google-sheets-import-export`. Set mode explicitly. Inspect uncharged `RUN_SUMMARY`: one entry per spreadsheet input with `inputIndex`, `status` (`ok`, `0-items`, `blocked`, `invalid`) and a short reason. Write previews retain dataset rows but have no actual writes; check `OUTPUT.rowsWritten` before confirming export. Explicit `onWriteError: fail` still fails the run.

Cap API spend with the run option `maxTotalChargeUsd`, separate from the `maxItems` row budget:

```javascript
const run = await client.actor('axiomworks/google-sheets-import-export').call({
    mode: 'read',
    spreadsheetUrl: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
    maxItems: 20,
}, { maxTotalChargeUsd: 1 });
const outcome = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('RUN_SUMMARY');
```

### FAQ

#### Do I need a Google account or OAuth app?

Not for reading sheets shared as "Anyone with the link". For private sheets and for writing you need a Google Cloud service account, which is free to create. There is no OAuth consent screen and no refresh token that expires.

#### Why does my run say "Sheet is private or does not exist"?

Google answers a request for a private sheet as if the sheet didn't exist. Either share the sheet as "Anyone with the link – Viewer", or provide `serviceAccountKey` and share the sheet with the key's `client_email`.

#### My write run succeeded but nothing changed in the sheet. Why?

Look at `_writeError` on the items, `writeError` in the `OUTPUT` record, and at the run's status message. With `onWriteError` set to `preview` (the default), a missing or rejected key, or a sheet not shared with the service account, turns the run into a dry-run preview instead of failing. Set `onWriteError` to `fail` if you prefer the run to fail.

#### How does append-dedupe decide what is a duplicate?

It joins the values of the `dedupeFields` columns into a key, compares it with every row already in the tab and with rows earlier in the same batch, and skips matches. The number of skipped rows is `rowsSkippedAsDuplicate` in the summary.

#### Are there size limits?

Google allows 10 million cells per spreadsheet and 50,000 characters per cell. The Actor cuts longer cell text down to 50,000 characters, and refuses a write that would push the spreadsheet past the cell limit, before writing anything. Large exports are sent in chunks of up to 2,000 rows.

### Is it legal to scrape Google Sheets?

This Actor doesn't scrape anyone else's content. It reads sheets that are already shared publicly by their owner, or sheets you have explicitly shared with your own service account, and it writes only to sheets you have given it Editor access to. Spreadsheets can contain personal data. If yours do, you are responsible for handling that data lawfully, for example under GDPR. This Actor is not affiliated with, endorsed by or sponsored by Google. Use the Actor only with spreadsheets you are allowed to access and change.

### Feedback

Found a bug, or need another mode or option? Open an issue on the Issues tab of this Actor with the run ID and a short description, and we'll look into it.

# Actor input Schema

## `spreadsheetUrl` (type: `string`):

Required URL or ID; sheetName overrides URL gid. One spreadsheet per run. Google Sheets URL or bare spreadsheet ID to read from or write to, e.g. 'https://docs.google.com/spreadsheets/d/<id>/edit#gid=0'. A #gid= in the URL selects that tab. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Omitted mode infers append when datasetId/rawData exists (append-dedupe with dedupeFields), otherwise read. Set explicitly for agents. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Omitted selects URL gid or first tab. Overrides URL gid; a tab name inside range is used when sheetName is omitted. Tab (worksheet) name to use, e.g. 'Class Data'. Overrides the gid in the URL. In write modes the tab is created when it does not exist. Default: the tab from the URL, else the first tab. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `range` (type: `string`):

Omitted reads the whole tab. A tab-qualified range supplies sheetName only when sheetName is omitted. Read selection uses headerRow and offset. Optional A1 range to read, e.g. 'A1:C10' or 'Class Data!A1:C10' (the tab prefix sets the tab name). Read mode only. Default: the whole tab. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `maxItems` (type: `integer`):

Shared row budget for this run, applied after read offset and before write dedupe; 0 is unlimited. Omitted: 1000 read rows or unlimited write rows. Limit rows to process, 0-1000000. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Default 0 skips no data rows. Read only; applied before maxItems. Read mode: number of data rows to skip after the header before reading, e.g. 100 to continue after a previous run's first 100 rows. Applied before maxItems. Default 0. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Omitted keeps all columns. Read headers or write keys; excludeColumns applies afterward. Only output/write these columns (read: sheet header names; write modes: item keys, 'a' also keeps 'a/b'), in this order, e.g. \['Name', 'Email']. Read metadata fields are always included. Default: all columns. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `excludeColumns` (type: `array`):

Omitted excludes none. Applied after columns in reads and writes. Columns to drop in every mode (read: sheet header names; write: item keys, 'a' also drops 'a/b'), e.g. \['internalId']. Applied after 'columns'. Default: none. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `headerRow` (type: `integer`):

Default 1 uses the first selected-range row as read headers. Read only; offset skips subsequent data rows. 1-based header row within the selected read range, e.g. 2 when its first row is a title. Accepts integers 1 or greater; default 1. Rows before it are skipped. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `serviceAccountKey` (type: `string`):

Omitted supports public reads; private reads and writes need a key and sheet sharing. Write access errors follow onWriteError. Full service-account JSON key file as a string, e.g. a JSON document containing client_email and private_key. Never paste credentials into prompts or public output. Omit for public reads. For private reads/writes, share the sheet with client_email as Viewer/Editor. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Omitted uses integration resource.defaultDatasetId if present. Write source; rawData takes precedence when both are supplied. Apify dataset ID whose items are written to the sheet in write modes, e.g. 'aBcD1234efGH5678', or '{{resource.defaultDatasetId}}' in an integration. Empty in an integration run: the triggering run's default dataset is used. Use this or rawData. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Omitted uses datasetId/integration dataset. Write source; takes precedence over datasetId. JSON array of objects to write instead of a dataset, e.g. \[{"url": "https://a.example", "title": "A"}]. Each object becomes one sheet row; keys become columns. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `dedupeFields` (type: `array`):

Omitted performs no dedupe in other modes; required for append-dedupe. Uses output column names after flattenNested. Column names that identify a row in append-dedupe mode, e.g. \['url']. Rows whose values already exist in the sheet (or earlier in the batch) are skipped. Required for append-dedupe. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `columnsOrder` (type: `array`):

Omitted uses first-seen key order. Write only; applies when the tab has no existing header. Fixed header order when the tab has no header yet, e.g. \['title', 'url']. Remaining keys follow in first-seen order. Default: first-seen order. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `flattenNested` (type: `boolean`):

Default true flattens write objects/arrays into path columns; false uses JSON text. Read ignores this. Whether to flatten nested write values, e.g. true converts {"address":{"city":"Prague"}} to an 'address/city' column and arrays to 'tags/0'. false writes nested values as JSON text. Default true. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `escapeFormulas` (type: `boolean`):

Default true stores formula-like write text safely. Interacts with valueInputOption; read ignores this. Whether to prefix write strings starting with =, +, - or @ with an apostrophe, e.g. true stores '=SUM(A1)' as text. false allows Google to interpret these strings with USER_ENTERED. Default true. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `valueInputOption` (type: `string`):

Default USER_ENTERED lets Google parse write values; RAW retains sent values. escapeFormulas still applies. Read ignores this. Google write parsing, e.g. 'RAW' to retain values as sent or 'USER_ENTERED' to parse numbers/dates like typing in Sheets. escapeFormulas still applies when enabled. Default USER_ENTERED. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

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

Default false writes rows. True previews without writing; read ignores this. Preview rows are uncharged. Whether to plan write rows without changing the sheet, e.g. true for a preview. Preview items have \_dryRun: true; rowsWritten is 0. Read mode ignores this setting. Default false. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## `onWriteError` (type: `string`):

Default preview returns uncharged planned rows with \_dryRun and \_writeError if write access fails. fail preserves the explicit failure contract. Action when write authentication or sheet access fails, e.g. 'preview' returns planned rows with \_dryRun: true and \_writeError; nothing is written. 'fail' ends the run with an error. Check OUTPUT.rowsWritten to confirm writes. Default preview. Uses maxItems row budget; bad/blocked/empty reads succeed with 0 items; write errors follow onWriteError.

## Actor input object example

```json
{
  "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0",
  "mode": "read",
  "maxItems": 20,
  "offset": 0,
  "headerRow": 1,
  "flattenNested": true,
  "escapeFormulas": true,
  "valueInputOption": "USER_ENTERED",
  "dryRun": false,
  "onWriteError": "preview"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Rows read from the tab, or rows written (or planned in a dry run) in write modes

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

Read and write totals for the run

## `runSummary` (type: `string`):

Uncharged per-input outcome: ok, 0-items, blocked or invalid, with a short reason. Write previews preserve dataset behavior; consult OUTPUT.rowsWritten before confirming a write.

# 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 = {
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0",
    "mode": "read",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/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 = {
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0",
    "mode": "read",
    "maxItems": 20,
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,axiomworks/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/NVpCLzw7XxUDQVTPO/builds/eUHFc1hanugUsvb6K/openapi.json
