Google Apps Script is a way to add your own code to Google Sheets, Docs, Forms and Gmail. It uses JavaScript, it runs on Google’s servers, and it lets you do things a formula cannot: send an email when a row changes, add a custom menu, tidy up data every night, or turn a sheet into a small web app. If you have ever repeated the same clicks in a spreadsheet every day, Apps Script is how you make the computer do them for you.
This is the first post of a series about automating Google Sheets. It explains what Apps Script is, where to write it, and the handful of ideas you need before the rest of the series: reading and writing cells, custom functions, menus and triggers.
In this guide
The short version
- Apps Script is JavaScript that runs on Google’s servers and controls Google Workspace: Sheets, Docs, Gmail, Calendar, Drive and more.
- Open it from a sheet with Extensions > Apps Script. A script created this way is bound to that sheet.
- You work with a sheet through
SpreadsheetApp:getRange(),getValues(),setValue(),appendRow(). - Triggers run your code automatically: when a cell is edited, when the file opens, or on a schedule.
- Runs are limited: for example 6 minutes per execution and a daily email limit. Check the current limits before you rely on them.
How the results in this post were produced. Apps Script runs only on Google’s servers, so the exact code shown was run against a small simulation of the Sheets service (an in-memory sheet with the same method names). The logic of the script is real, but the sheet and the log are simulated, and the clock was fixed at Monday 16 March 2026, 09:30 India time. Always try a script on a copy of your own sheet first.
What is Apps Script?
Google describes Apps Script as a platform for building business applications that work with Google Workspace, written in modern JavaScript. In practice you can:
- add custom menus, dialogs and sidebars to Sheets, Docs and Forms;
- write custom functions that you use in a cell like
=SUM(); - run code automatically with triggers (on edit, on open, on a schedule);
- talk to other Google services, such as Gmail, Calendar and Drive;
- publish a web app that anyone with the link can open.
You do not need to install anything. The editor is in your browser, and your code is saved in your Google account.
Where you write it
Open a Google Sheet and choose Extensions > Apps Script. A new browser tab opens with the script editor. A script created this way is container-bound: it belongs to that spreadsheet, and it can use the sheet directly, without any setup. This is what you want for almost everything in this series. The other kind is a standalone script, created from script.google.com, which is not tied to one file.
The editor has a file list on the left, the code in the middle, and a Run button with a function selector at the top. Output appears in the Execution log at the bottom. Any function you write can be run from there.
Your first script
A function is a named block of code. Logger.log() writes a line to the Execution log, which is the Apps Script equivalent of print:
function hello() {
Logger.log("Hello from Apps Script");
}
Execution log (simulated)
Hello from Apps Script
The first time you run a script that touches your data, Google shows an authorization dialog. Apps Script works out which permissions your code needs, for example access to your spreadsheets, and asks you to approve them. Scripts that use sensitive permissions can show a warning that the app is unverified. For a script you wrote yourself that is expected, because Google has not reviewed it. Read what it asks for before you continue.
Reading a sheet
Everything about the spreadsheet goes through SpreadsheetApp. getActiveSpreadsheet() gives you the file, getSheetByName() gives you a tab, and getDataRange().getValues() reads all the cells that hold data into a two-dimensional array: an array of rows, where each row is an array of cells. Here is a small sales sheet, which we will use in the rest of this post:
| A | B | C | D | |
|---|---|---|---|---|
| 1 | Order | Product | Amount | Status |
| 2 | 1001 | Notebook | 250 | Paid |
| 3 | 1002 | Pen | 40 | Pending |
| 4 | 1003 | Bag | 900 | Paid |
The script below adds up the Amount column. Look at how the array is used: rows[i][2] means “row i, column 3”.
function totalSales() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
const rows = sheet.getDataRange().getValues(); // a 2D array: rows[row][column]
let total = 0;
for (let i = 1; i < rows.length; i++) { // start at 1 to skip the header row
total += rows[i][2]; // the third column (index 2) is Amount
}
Logger.log("Total: %s", total);
return total;
}
Execution log (simulated)
Total: 1190
The one confusing thing: 1 or 0?
Beginners trip on this more than anything else. Methods such as getRange(row, column) count from 1, like the row numbers you see in the sheet. But the arrays returned by getValues() are ordinary JavaScript arrays, which count from 0. So the same cell has two different addresses. Look at the cell C3 (row 3, column 3), read in three ways:
function showIndexes() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
const values = sheet.getDataRange().getValues();
Logger.log(sheet.getRange(3, 3).getValue()); // row 3, column 3: counting starts at 1
Logger.log(sheet.getRange("C3").getValue()); // the same cell, in A1 notation
Logger.log(values[2][2]); // the same cell in the array: counting starts at 0
}
Execution log (simulated)
40
40
40
Remember: sheet coordinates start at 1, array positions start at 0. When you loop over the array and need to write back to the sheet, add 1 to convert the array index into a row number.
Writing to a sheet
setValue() writes into one cell, and appendRow() adds a whole row after the last row that has data. getLastRow() tells you where that is. This function records a new order and marks it as paid:
function addOrder() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
sheet.appendRow([1004, "Pencil box", 120]); // adds a row after the last row with data
const last = sheet.getLastRow();
sheet.getRange(last, 4).setValue("Paid"); // column 4 = Status
Logger.log("Added row %s", last);
}
Execution log (simulated)
Added row 5
| A | B | C | D | |
|---|---|---|---|---|
| 1 | Order | Product | Amount | Status |
| 2 | 1001 | Notebook | 250 | Paid |
| 3 | 1002 | Pen | 40 | Pending |
| 4 | 1003 | Bag | 900 | Paid |
| 5 | 1004 | Pencil box | 120 | Paid |
Row 5 was created by appendRow() with three values, and then setValue() filled column 4.
Custom functions
A custom function is a normal function that you call from a cell, like a built-in formula. If you write =WITHGST(C2) in a cell, Sheets runs your function with the value of C2 and shows what it returns. Custom functions have rules: the name must not clash with a built-in function or end in an underscore, the call must return within 30 seconds (otherwise the cell shows an error), and the function cannot change other cells. It returns a value, and that is all. The @customfunction line in the comment makes the function show up in Sheets’ autocomplete with its description:
/**
* Adds 18% tax to an amount.
*
* @param {number} amount The price before tax.
* @return {number} The price with tax.
* @customfunction
*/
function WITHGST(amount) {
return Math.round(amount * 1.18 * 100) / 100;
}
Execution log (simulated)
295
47.2
So =WITHGST(250) shows 295, and it works on any cell you point it at.
A custom menu
You can put your own menu in the Sheets toolbar so that people can run a function with a click. A special function named onOpen() runs automatically when the file is opened, and it is where menus are created. This is the smallest example. It gets its own post in this series (see below):
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Upskly Tools")
.addItem("Add a sample order", "addOrder")
.addToUi();
}
Execution log (simulated)
Menu "Upskly Tools": Add a sample order -> addOrder()
Triggers: running scripts automatically
Up to now you ran functions by hand. The real power of Apps Script is triggers, which run a function automatically:
| Trigger | Runs when | Covered in |
|---|---|---|
| onOpen() | Someone opens the file | onOpen in Apps Script |
| onEdit(e) | Someone edits a cell | onEdit in Apps Script |
| Time-driven | Every minute, hour, day, week or month | Apps Script Time Triggers |
| doGet(e) and doPost(e) | Someone visits, or sends data to, your web app | What Is doGet in Apps Script? |
Some triggers are simple: you just name a function onEdit or onOpen, and it works with no set-up, but with some limits (for example, no sending email). Others are installable: you create them yourself, and they can do more. The next posts explain the difference in detail.
Limits to know about
Apps Script is free to use with a Google account, but it runs within quotas. These are the figures in Google’s documentation at the time of writing (September 2026). They can change, so check the official quotas page before you depend on a number:
| Limit | Personal (gmail.com) account | Google Workspace account |
|---|---|---|
| Email recipients per day | 100 | 1,500 |
| Total trigger runtime per day | 90 minutes | 6 hours |
| URL Fetch calls per day | 20,000 | 100,000 |
| One script run | 6 minutes | 6 minutes |
| One custom function call | 30 seconds | 30 seconds |
| Triggers per user per script | 20 | 20 |
None of this matters for small automations. It matters when a script loops over thousands of rows or sends many emails. The habit that keeps you inside the limits is to read a range once with getValues() and write it once with setValues(), instead of touching cells one at a time.
Apps Script or a formula?
| You want to… | Use |
|---|---|
| Calculate something in a cell | A formula |
| Reuse a calculation that formulas make messy | A custom function |
| Do something when a cell changes or a file opens | A simple trigger |
| Run something every morning or every hour | A time-driven trigger |
| Send an email, or call another Google service | An installable trigger or a menu item |
| Give people a form or a page on top of a sheet | A web app |
Start with a formula. Reach for Apps Script when you need something to happen rather than something to be calculated.
Common mistakes
- Mixing up 1-based and 0-based. Sheet rows and columns start at 1, and arrays start at 0.
- Reading or writing one cell at a time in a loop. Every call takes time. Read a whole range with
getValues(), work on the array, and write it back withsetValues(). - Skipping the header row.
getDataRange()includes it, so start the loop at index 1. - Wrong sheet name.
getSheetByName("sales")is not"Sales". When the name does not match, it returnsnull, and the next line fails. - Expecting a simple trigger to do everything. Simple triggers cannot use services that need authorization, and they stop after 30 seconds.
- Testing on the real sheet. Make a copy of your sheet and try the script there first.
The rest of this series
Each next post takes one narrow topic and explains it with use cases:
- onEdit in Apps Script: How It Works, Use Cases
- onOpen in Apps Script: How It Works, Use Cases
- Apps Script Time Triggers: Cron for Google Sheets
- What Is doGet in Apps Script? Explained
- Build a Web App Frontend With Apps Script
- Apps Script Deployment Types Explained
Apps Script is JavaScript, so if you know Python you will pick the ideas up quickly. If you have not learned to code yet, How to Learn Python: A Step-by-Step Roadmap teaches the same basics (variables, loops, functions) in a language with simpler syntax. The official Apps Script documentation is the place to check details.
Try it yourself
Use the sales sheet from above. Work out each answer first, then open the solution.
1. Write a function that logs how many orders are in the sheet.
Show solution
function countOrders() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
Logger.log(sheet.getLastRow() - 1); // minus the header row
}
Execution log (simulated)
3getLastRow() is the number of the last row that has data, and one of those rows is the header.
2. Log the amount of the biggest order.
Show solution
function biggestOrder() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
const rows = sheet.getDataRange().getValues().slice(1); // drop the header
const amounts = rows.map(row => row[2]);
Logger.log(Math.max(...amounts));
}
Execution log (simulated)
900slice(1) drops the header, map() picks the Amount column, and Math.max(...) finds the largest.
3. Set the Status of every order to “Paid” with a single write.
Show solution
function markAllPaid() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales");
const count = sheet.getLastRow() - 1;
const values = [];
for (let i = 0; i < count; i++) values.push(["Paid"]);
sheet.getRange(2, 4, count, 1).setValues(values); // one write for the whole column
}
| A | B | C | D | |
|---|---|---|---|---|
| 1 | Order | Product | Amount | Status |
| 2 | 1001 | Notebook | 250 | Paid |
| 3 | 1002 | Pen | 40 | Paid |
| 4 | 1003 | Bag | 900 | Paid |
The values must be a 2D array with one row per order, so [["Paid"], ["Paid"], ["Paid"]]. One setValues() call is much faster than three setValue() calls, and the gap grows with the number of rows.
4. Why is values[2][2] the same cell as getRange(3, 3)?
Show answer
Sheet methods count rows and columns from 1, and JavaScript arrays count from 0, so index 2 is the third row and the third column.
Frequently asked questions
What is Google Apps Script?
It is a platform that lets you write JavaScript to automate and extend Google Workspace: Sheets, Docs, Forms, Gmail, Calendar and Drive. Your code runs on Google’s servers.
Do I need to know JavaScript to use Apps Script?
Basic JavaScript is enough to start: variables, functions, loops and arrays. You can learn the rest as you go, and copying a small working example and changing it is a good way in.
How do I open the Apps Script editor in Google Sheets?
Open the sheet and choose Extensions, then Apps Script. A new tab opens with the editor, and the script is bound to that spreadsheet.
Is Apps Script free?
You can use it with any Google account without paying, within Google’s daily quotas, such as limits on run time and emails sent. Workspace accounts have higher limits.
What is the difference between a bound and a standalone script?
A bound script belongs to one file, such as a spreadsheet, and can use it directly. A standalone script is created on its own and is not tied to a file.
Can Apps Script run automatically?
Yes. Triggers run a function when a cell is edited, when a file opens, on a schedule (every minute up to once a month), or when someone visits a web app.
Related reading
- onEdit in Apps Script: How It Works, Use Cases – run code when a cell changes.
- Apps Script Time Triggers: Cron for Google Sheets – run code on a schedule.
- What Is doGet in Apps Script? Explained – turn a script into a web app.
- How to Learn Python: A Step-by-Step Roadmap – learn to code from scratch.