New: 6 free SQL practice datasets with 300+ questions — try the SQL Compiler →
Automations

What Is doGet in Apps Script? Explained

What is doGet in Apps Script? How web app URLs call doGet(e), read query parameters, return JSON or HTML, handle doPost and stay safe.

Upskly AI Team September 26, 2026 13 min read
What Is doGet in Apps Script? Explained

doGet is the function that Google Apps Script runs when someone opens the URL of your web app, or when a program sends it an HTTP GET request. Whatever doGet returns is what the visitor gets back: a web page, some plain text, or JSON data. That one function turns a script into a small website or a tiny API on top of your Google Sheet.

This post explains what doGet(e) is, what the event object e holds, and how to use it to return JSON from a sheet, greet a visitor safely, route by URL path, and how its sibling doPost receives data. It also covers the security choices that matter. If you are new to Apps Script, start with Google Apps Script for Beginners: A Simple Intro.

In this guide

The short version

  • doGet(e) runs when someone visits the web app URL or sends it a GET request. doPost(e) runs for POST requests.
  • It must return an HTML Service HtmlOutput or a Content Service TextOutput. Anything else is an error.
  • Read the query string from e.parameter (first value of each name) or e.parameters (arrays).
  • Nothing has a URL until you deploy the script as a web app, and code changes reach the public URL only in a new version.
  • The two settings that decide security: Execute as and Who has access. Never print raw visitor input into HTML.

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.

For a web app, the requests were simulated too: the harness builds the event object e that Google would pass for the URL shown.

What doGet is

Google lists doGet(e) and doPost(e) among the special functions of Apps Script, together with onOpen and onEdit. doGet runs “when a user visits a web app or an HTTP GET request arrives”. A script becomes a web app when it has a doGet or doPost function that returns either:

  • an HtmlOutput object (from HtmlService), which the browser shows as a web page, or
  • a TextOutput object (from ContentService), which is plain text, JSON, CSV or XML.

Both bound and standalone scripts can be web apps. Unlike onEdit, nothing is triggered by a spreadsheet. The trigger is an HTTP request.

The smallest doGet

This is a complete web app. The first function returns text and the second returns a tiny HTML page (you only need one doGet in a project, the second one has a different name so both can be shown):

function doGet() {
  return ContentService.createTextOutput("Hello from doGet");
}

function doGetHtml() {
  return HtmlService.createHtmlOutput("<h1>Hello from doGet</h1>");
}

Execution log (simulated)

Text output: Hello from doGet (type TEXT)
HTML output: <h1>Hello from doGet</h1>

The event object e

When a request arrives, Google passes an event object e to doGet. These are its main fields, from Google’s documentation:

The web app event object
FieldWhat it holds
e.queryStringThe query string of the URL, or null if there is none
e.parameterAn object of name to value. For a name that appears more than once, only the first value
e.parametersThe same, but every value is an array, so repeated names keep all their values
e.pathInfoThe URL path after /exec or /dev
e.postDataFor doPost only: the request body (contents), its MIME type and length

So a visit to .../exec?name=Asha&tag=a&tag=b gives:

function doGet(e) {
  Logger.log("queryString: %s", e.queryString);
  Logger.log("parameter: %s", JSON.stringify(e.parameter));
  Logger.log("parameters: %s", JSON.stringify(e.parameters));
  return ContentService.createTextOutput("ok");
}

Execution log (simulated)

queryString: name=Asha&tag=a&tag=b
parameter: {"name":"Asha","tag":"a"}
parameters: {"name":["Asha"],"tag":["a","b"]}
--- and a visit with no query string ---
queryString: null
parameter: {}
parameters: {}

The name tag was sent twice. e.parameter keeps only the first value, while e.parameters keeps both. With no query string, e.queryString is null and the objects are empty, so always give a default: e.parameter.name || "guest".

Use case 1: a JSON API from a sheet

Your sheet holds a product list, and you want another website or app to read it. doGet reads the rows, optionally filters them by ?category=, and returns JSON. Setting the MIME type to JSON tells the caller how to read it. The spreadsheet is opened by its id, which is the long part of the sheet’s URL, because a web app has no screen and no open file:

function doGet(e) {
  const rows = SpreadsheetApp.openById("YOUR_SPREADSHEET_ID")   // the id is in the sheet's URL
      .getSheetByName("Products").getDataRange().getValues().slice(1);
  const category = e.parameter.category;                          // undefined if the URL has no ?category=
  const items = rows
      .filter(row => !category || row[1] === category)
      .map(row => ({ name: row[0], category: row[1], price: row[2] }));

  return ContentService
      .createTextOutput(JSON.stringify({ count: items.length, items: items }))
      .setMimeType(ContentService.MimeType.JSON);
}

Responses (simulated)

no filter  [JSON]
{"count":4,"items":[{"name":"Notebook","category":"Stationery","price":250},{"name":"Pen","category":"Stationery","price":40},{"name":"Bag","category":"Accessories","price":900},{"name":"Bottle","category":"Accessories","price":350}]}
?category=Stationery  [JSON]
{"count":2,"items":[{"name":"Notebook","category":"Stationery","price":250},{"name":"Pen","category":"Stationery","price":40}]}
?category=Toys  [JSON]
{"count":0,"items":[]}

Because the script runs as its owner, visitors can read this data without having access to the sheet itself. That is powerful, and it is the reason to think about what you return (see the security section below).

Use case 2: an HTML page, safely

Returning HTML is just as easy, but it has one serious trap: if you paste the visitor’s text into the page, they can inject their own script. Look at what happens when someone opens a link with ?name=<script>steal()</script>:

function doGetUnsafe(e) {
  // BAD: the visitor's text goes straight into the HTML
  return HtmlService.createHtmlOutput("<h1>Hello, " + e.parameter.name + "</h1>");
}

function doGetSafe(e) {
  // GOOD: <?= ?> escapes the value before it is printed
  const page = HtmlService.createTemplate("<h1>Hello, <?= name ?></h1>");
  page.name = e.parameter.name || "guest";
  return page.evaluate();
}

Pages produced (simulated)

Unsafe: <h1>Hello, <script>steal()</script></h1>
Safe:   <h1>Hello, &lt;script&gt;steal()&lt;/script&gt;</h1>
Safe with no name: <h1>Hello, guest</h1>

The unsafe version prints the visitor’s script tag as it is, and a browser would run it. The template version, with <?= ?>, escapes the value, so the browser only shows it as text. Google’s documentation recommends the printing scriptlet <?= ?> as the default and warns to use the force-printing <?!= ?> only for content you fully control. Templates get their own treatment in Build a Web App Frontend With Apps Script.

Use case 3: routing by path

A web app has one URL, but e.pathInfo holds anything added after /exec, so one doGet can serve several “pages”:

function doGet(e) {
  switch (e.pathInfo) {                       // the part of the URL after /exec
    case "pricing":
      return ContentService.createTextOutput("Pricing page");
    case "about":
      return ContentService.createTextOutput("About page");
    default:
      return ContentService.createTextOutput("Home page");
  }
}

Responses (simulated)

/exec/  ->  Home page
/exec/pricing  ->  Pricing page
/exec/about  ->  About page
/exec/contact  ->  Home page

doPost: receiving data

GET is for asking. When another system needs to send data, for example a form on another site, or a webhook, it makes a POST request, and doPost(e) runs. The body of the request is in e.postData.contents, as text, and its type in e.postData.type. This one adds a signup to a sheet and answers with JSON:

function doPost(e) {
  const data = JSON.parse(e.postData.contents);                   // the body of the request, as text
  const sheet = SpreadsheetApp.openById("YOUR_SPREADSHEET_ID").getSheetByName("Signups");
  sheet.appendRow([data.name, data.email]);
  return ContentService
      .createTextOutput(JSON.stringify({ status: "ok", row: sheet.getLastRow() }))
      .setMimeType(ContentService.MimeType.JSON);
}

Response and sheet (simulated)

Response: {"status":"ok","row":2}
Signups after the POST (simulated sheet)
AB
1NameEmail
2Ashaasha@example.com
doGet and doPost
doGet(e)doPost(e)
Runs onHTTP GET (visiting a URL)HTTP POST (sending data)
Data arrives inThe URL: e.parameter, e.pathInfoThe body: e.postData.contents
Typical useShow a page, return dataReceive a form or a webhook, write a row
Must returnHtmlOutput or TextOutputHtmlOutput or TextOutput

Getting a URL: deploy as a web app

Until you deploy it, your doGet has no address. In the editor:

  1. At the top right, click Deploy, then New deployment.
  2. Next to “Select type”, choose Web app.
  3. Fill in the description and the two access settings (below).
  4. Click Deploy, approve the permissions, and copy the web app URL. It ends in /exec.

There is also a test deployment with a URL ending in /dev. Only people with edit access to the script can open it, and it always runs your latest saved code. The full picture, with versions and updates, is in Apps Script Deployment Types Explained.

Who can call it

Two settings control the safety of a web app. Execute as decides whose account runs the script: you (the owner, always) or the user who is using the web app. Who has access decides who may open the URL. In the manifest file (appsscript.json) the four access values are:

webapp.access
ValueMeaning
MYSELFOnly the person who deployed it
DOMAINOnly users in the same Google Workspace domain as the deployer
ANYONEAny signed-in Google user
ANYONE_ANONYMOUSAnyone, even if not signed in

The deployment dialog offers the same choices in plain words. The combination to be careful with is Execute as me plus Anyone: then strangers run code with your permissions. That is fine for a small public read-only feed, but you must make sure that:

  • the script returns only data you are happy to publish (never the whole sheet by accident);
  • visitor input is treated as untrusted: escape it in HTML, and validate it before it goes into a sheet;
  • no secrets, such as API keys, appear in what it returns;
  • write actions (doPost) check what they receive, since anyone with the URL can call them.

Testing and updating

You cannot test doGet(e) by pressing Run, because nothing passes an event, and e.parameter fails on undefined. Either open the /dev URL of a test deployment in your browser, or write a test function that calls doGet with a made-up event, as the examples on this page do. Then check Executions for errors.

Updating is the step people forget. The /exec URL serves a version of your code. After changing the code you have to create a new version and point the deployment at it (Deploy, then Manage deployments, then edit the deployment). The URL stays the same. Only the /dev URL always runs the latest saved code.

Common mistakes

  • Returning the wrong thing. doGet must return an HtmlOutput or TextOutput. Returning a string or an object gives an error page.
  • Forgetting to redeploy. Saving the code does not change the /exec URL. Create a new version and update the deployment.
  • No default for missing parameters. e.parameter.name is undefined when the URL has no ?name=. Use || "default".
  • Printing visitor input as raw HTML. Use a template with <?= ?> so that the value is escaped.
  • Relying on getActiveSpreadsheet(). A web app has no open file, so open the sheet explicitly with SpreadsheetApp.openById().
  • Wrong access setting. If visitors see a sign-in or permission page, “Who has access” is too narrow for them.
  • Exposing too much. With “Execute as me” and “Anyone”, everything the script returns is public. Return only what you mean to share.
  • Testing with the Run button. There is no event. Use the /dev URL or a test function.

Try it yourself

Work out each answer first, then open the solution.

1. Write a doGet that returns the current date and time as plain text.

Show solution
function doGet() {
  const now = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm");
  return ContentService.createTextOutput("The time is " + now);
}

Response (simulated)

The time is 2026-03-16 09:30

ContentService.createTextOutput() returns a TextOutput, which is allowed. Utilities.formatDate() formats the time in the script’s time zone.

2. Greet the visitor with ?name=, and say “friend” if no name is given. Do it safely.

Show solution
function doGet(e) {
  const name = e.parameter.name || "friend";
  const page = HtmlService.createTemplate("<p>Welcome, <?= name ?>!</p>");
  page.name = name;
  return page.evaluate();
}

Pages produced (simulated)

<p>Welcome, Asha!</p>
<p>Welcome, friend!</p>

The value goes into a template and is printed with <?= ?>, which escapes it.

3. Return the number of products in the sheet as JSON.

Show solution
function doGet() {
  const count = SpreadsheetApp.openById("YOUR_SPREADSHEET_ID")
      .getSheetByName("Products").getLastRow() - 1;                // minus the header row
  return ContentService.createTextOutput(JSON.stringify({ products: count }))
      .setMimeType(ContentService.MimeType.JSON);
}

Response (simulated)

{"products":4}

The last row minus the header is the number of products. Setting the MIME type to JSON tells the caller how to read the reply.

4. A URL contains ?id=7&id=9. What are e.parameter.id and e.parameters.id?

Show answer

e.parameter.id is the first value, "7". e.parameters.id is the array ["7", "9"]. All values arrive as text, so convert with Number() when you need a number.

Frequently asked questions

What is doGet in Google Apps Script?

It is a special function that runs when a user visits a web app URL or when an HTTP GET request is sent to it. It receives an event object e and must return an HtmlOutput or a TextOutput.

What is the difference between doGet and doPost?

doGet handles GET requests, where the data is in the URL. doPost handles POST requests, where the data is in the request body (e.postData). Both must return an HtmlOutput or TextOutput.

How do I get the URL parameters in doGet?

Use e.parameter for the first value of each name, or e.parameters for arrays of all values. For example, e.parameter.name for ?name=Asha.

How do I return JSON from Apps Script?

Return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON) from doGet.

Why do I get an error when I run doGet in the editor?

The editor calls the function without a request, so e is undefined. Deploy the script and open the test URL, or call doGet from a test function with a made-up event.

Is a doGet web app public?

It depends on the deployment setting “Who has access”. It can be only you, your domain, any signed-in Google user, or anyone. Combined with “Execute as me”, think carefully about what your script returns.

Upskly AI Team
Learning made simple
Scroll to Top