◉ANTIGRAVITY LABJP
Articles/Integrations
⬡ Integrations/2026-11-03Beginner

Turning a Spreadsheet Chore into Apps Script: The First Request a Non-Engineer Can Hand to Antigravity

For non-engineers who want a weekly copy-and-paste routine handled by Apps Script: how to word the request to Antigravity, build in a dry run, keep the data safe, set a trigger, and sort out the usual snags.

antigravity471apps-scriptgoogle-sheetsautomation96beginner19

Friday evenings, moving the same rows in the same order

I would find every row whose Status column said Shipped, copy it to another sheet, paste it, and delete the original. Every Friday, around dusk.

It took under ten minutes. Yet just before deleting, my hand stopped every single time. Did I paste into the right sheet? Was my selection off by one row? That small worry made ten minutes feel twice as long.

When I finally decided to hand this to Apps Script, my first request to Antigravity was simply "automate this."

My first request was far too vague

The code that came back worked. It also deleted the moved rows outright, with no way to keep the originals and no way to try it safely.

That was my doing. I hadn't said which columns mattered, what to do on failure, or what counted as "done." The problem was the request, not the code.

Anything that deletes gets a dry run and a safe order first. For a first automation, I think that single rule is enough to carry.

Four things to put in the request

Writing these four points makes the returned code noticeably safer.

What to includeExample wording
Sheet and column namesSheet "Orders", header in row 1, columns Date, Item, Qty, Price, Status
The conditionOnly rows where Status is Shipped
Where rows goAppend below the last row of the "Done" sheet
A safety switchA flag that only reports what it would move, without changing anything

A request along these lines works well:

Write one Apps Script for Google Sheets.
- In the "Orders" sheet, row 1 is the header: Date, Item, Qty, Price, Status
- Append every row whose Status is "Shipped" to the end of the "Done" sheet
- Then delete only those rows from "Orders"
- Put a DRY_RUN constant at the top. While it is true, change nothing
  and just log how many rows would move
- If a sheet or header is missing, throw an error that says what is missing

Adding "write comments in plain language" helps when you reread it later.

Reading what came back

This is the version I settled on. It looks long, but it only does three things: find, move, delete.

// While true, nothing is written; the script only logs how many rows would move
const DRY_RUN = true;
 
function archiveShipped() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const src = ss.getSheetByName('Orders');
  const dst = ss.getSheetByName('Done');
  if (!src || !dst) {
    throw new Error('Sheet not found. Check the names "Orders" and "Done"');
  }
 
  const values = src.getDataRange().getValues();
  const statusCol = values[0].indexOf('Status');
  if (statusCol === -1) {
    throw new Error('No "Status" header in row 1');
  }
 
  // Skip the header row and collect only the Shipped rows
  const moving = values.slice(1).filter(row => row[statusCol] === 'Shipped');
  Logger.log('Would move: ' + moving.length + ' rows (DRY_RUN=' + DRY_RUN + ')');
  if (moving.length === 0 || DRY_RUN) return;
 
  // Append first; delete only after that succeeds
  dst.getRange(dst.getLastRow() + 1, 1, moving.length, values[0].length)
     .setValues(moving);
 
  // Delete from the bottom so row numbers don't shift under us
  for (let i = values.length - 1; i >= 1; i--) {
    if (values[i][statusCol] === 'Shipped') src.deleteRow(i + 1);
  }
}

Three things to look for:

  • DRY_RUN ends the function before anything is written
  • The append (setValues) comes before the delete (deleteRow)
  • Deletion runs bottom-up, because deleting from the top shifts the next row up and you skip it

You don't have to understand every line. If one of the three is missing, ask Antigravity to add a DRY_RUN switch and bottom-up deletion, and it will.

Paste, rehearse, then go live

Short steps:

  1. Open the spreadsheet, then choose Extensions and Apps Script.
  2. Clear the contents of Code.gs, paste the code, and save.
  3. Pick archiveShipped in the function dropdown and press Run.
  4. The first run shows an authorization prompt. Follow it and allow access with your own account.
  5. If the execution log says "Would move: N rows," the rehearsal worked and the sheet is untouched.

Check that N matches what you expect, set DRY_RUN to false, and run it once more.

Before going live, I make a copy of the whole spreadsheet. It costs a minute, and the relief is out of proportion to it.

Running it weekly with a trigger

Click the clock icon (Triggers) in the editor's left menu, then Add Trigger.

Choose archiveShipped as the function, Time-driven as the event source, Week timer as the type, and pick a day and hour.

I chose early Saturday morning instead of Friday evening. A script that fires while someone is mid-edit can catch a row halfway through being updated.

Telling the usual snags apart

SymptomSuspect first
"Sheet not found"Stray spaces around the name, or full-width versus half-width characters
Count stays at 0Whitespace in the Status values (such as `'Shipped '`)
The authorization screen keeps returningSeveral signed-in accounts, with the wrong one selected
The trigger doesn't seem to fireThe Executions page in the left menu, for a recorded failure reason

If the count stays at 0, trim values before comparing: String(row[statusCol]).trim() === 'Shipped' absorbs differences you can't see by eye. Asking Antigravity to "ignore surrounding whitespace when comparing" gets you the same thing.

What to do next

Pick the one chore that makes your hand stop most often, and write the request with those four points, in a form that never deletes without a rehearsal.

Once it works, let next week's version of you hand it over. I spend those ten minutes back on the work outside the spreadsheet.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Integrations2026-10-17
When Antigravity's Browser Agent Stops at the Login Screen: Four Things to Check
The browser agent came back with only a sign-in page. Here are four checks, built on the fact that it runs in a separate Chrome profile, to get its login state sorted.
⬡ Integrations2026-09-12
Antigravity CLI 1.1.28 turns a print-timeout overrun into a successful exit — rechecking unattended runs
Since 1.1.28, an expired --print-timeout returns partial output and exits successfully instead of failing. Fetching an external URL now asks for approval first. Here is how I rebuilt an unattended pipeline that judged everything by the exit code.
⬡ Integrations2026-09-05
Pointing GOOGLE_GEMINI_BASE_URL at Your Own Gateway Does Not Change Who Holds the Key
I pointed the Antigravity CLI at a gateway of my own and recorded exactly which credentials travelled with it. Authorization is dropped across a cross-origin redirect, but the API key header survives every hop. Here is the measurement, and how I moved the key off my laptop.