// Sheets · Apps Script

Fix "Service Spreadsheets failed while accessing document" in Google Sheets.

The Spreadsheet service throws "Service Spreadsheets failed while accessing document" when a single setValues call touches too many cells at once, or when the service is transiently overloaded. Fix it by chunking large writes and wrapping calls in a retry loop with exponential backoff.

I'm getting "Service Spreadsheets failed while accessing document with id" in my Apps Script and I can't tell if it's my code or Google's servers.

The script

copy · paste · trigger
chunkedWrite.gs
Apps Script
// Write a 2-D array to a sheet in batches; retries each chunk on failure.
// Tune CHUNK_ROWS down if you still hit quota on wide sheets.
const CHUNK_ROWS = 2000;
const MAX_RETRIES = 3;

function chunkedWrite(sheet, startRow, data) {
  for (var i = 0; i < data.length; i += CHUNK_ROWS) {
    var chunk = data.slice(i, i + CHUNK_ROWS);
    var range = sheet.getRange(startRow + i, 1, chunk.length, chunk[0].length);
    writeWithRetry(range, chunk);
    SpreadsheetApp.flush();
  }
}

function writeWithRetry(range, values) {
  for (var attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      range.setValues(values);
      return;
    } catch (e) {
      if (attempt === MAX_RETRIES - 1) throw e;
      Utilities.sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What the error actually means

"Service Spreadsheets failed while accessing document" is the Spreadsheet service reporting that it could not complete an operation on the document — not that your code is syntactically wrong, and not that you lack permission. Google's own infrastructure either hit a transient fault on their end or your script asked for more work than the service is willing to do in one round-trip.

The most common trigger is a single setValues call covering a very large range: 20,000 cells in one go is usually fine; 200,000 in one call is asking for trouble, especially on sheets with conditional formatting, data validation, or named ranges that force recalculation. The second trigger is pure transience — the same call succeeds if you run it again thirty seconds later, which is the tell.

The two-pronged fix: chunking and retry

Chunking means splitting your data array into slices of a few thousand rows, writing each slice with its own getRange/setValues call, then calling SpreadsheetApp.flush() to force the write to the server before moving to the next slice. flush() is the piece people skip: without it, Apps Script may batch the operations internally and then flush everything at the end of the function, which puts you right back into the oversized-single-call problem.

The retry loop handles the transient case. Exponential backoff — 1 second after the first failure, 2 after the second, 4 after the third — is the pattern Google's own Apps Script documentation recommends for quota and service errors. I keep MAX_RETRIES at 3 in practice; more than that and you are masking a real structural problem rather than a blip.

CHUNK_ROWS at 2000 is a conservative starting point. If your sheet is narrow (fewer than 10 columns), you can push it to 5000 without issue. If it is very wide (50+ columns) or heavily formatted, drop it to 500 and watch whether the error clears.

Other causes worth ruling out

If chunking and retry do not help, check whether the script is running under a service account or as a different user than the document owner. Shared drives with strict ACLs occasionally produce this error when the executing identity does not have edit rights, even though the error message sounds like a service fault rather than a permission fault.

Concurrent executions hitting the same spreadsheet can also produce it. Apps Script does not lock the spreadsheet between API calls; if you have a time-driven trigger firing every minute and your script takes longer than a minute to run, two instances will overlap and race on the same document. The fix there is to use LockService.getDocumentLock() at the top of the function and release it in a finally block — nothing to do with batching.

Very occasionally this error appears on getValues rather than setValues, usually when reading a range that spans a named range boundary or a merged cell region that Apps Script cannot resolve cleanly. In that case, narrow the read range until you isolate which cells are triggering it.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Does this error mean I've hit Google's daily quota for Spreadsheet operations?
Usually not. Quota exhaustion produces a different error: "Service invoked too many times for one day" or "Quota exceeded for quota metric 'spreadsheets.readonly'." The "failed while accessing document" message is almost always a per-call size issue or a transient fault, not a daily cap.
How many cells can I write in a single setValues call before it starts failing?
There is no hard documented limit, but in practice calls touching more than roughly 40,000 to 50,000 cells become unreliable, and anything above 100,000 cells fails consistently. The threshold varies with sheet complexity (formatting, formulas, data validation all reduce it). Staying under 20,000 cells per call is safe.
Will SpreadsheetApp.flush() slow down my script significantly?
Yes, each flush() is a network round-trip, typically 200 to 600 ms. On a 100,000-row write chunked at 2,000 rows, that is 50 flushes, which adds 10 to 30 seconds. That is the correct trade-off: a reliable 30-second write beats a 5-second write that fails and leaves the sheet in a partial state.
The error only happens in the middle of the night when my trigger runs — why?
Time-driven triggers share execution infrastructure with every other Apps Script trigger firing at that time. Midnight and the top of every hour are peak times because that is when most scheduled triggers are set. The transient form of this error is more frequent during those windows. Adding the retry loop is usually enough; if it is not, shift your trigger off the hour by 7 to 13 minutes.