Google Apps Script: Web Apps
Did you know that you can easily create an interactive webpage web app that you can embed in your own website or your Google Workspace domain for internal use with your organisation with Google Apps Script?
Perhaps you want to create a small online app using resources you have in your Google workspace, like Google Sheets or BigQuery.
Maybe the functionality of Google Forms is not enough and you want to build a custom form for something like a seat booking form with a way for users to register a bunch of participants and immediately see if there are seats available. Or create a form where each answer creates a new set of questions that you want to add to a Google Sheet in our own way.
What if you wanted to road test a new app idea and want to use the development speed of Google Apps Script to get a proof of concept up and running in a flash?
Fortunately for you, Google Apps Script has you covered with the ability to deploy Web Apps.
Even if you have been working in the Google Apps Script environment for a while, getting started on Web Apps can be a bit of a daunting task. It’s just that little bit different that it can make you apprehensive at first. However, let me tell you that once you master the few basics, you will be smashing out Web Apps in no time.
In this tutorial, we will go over the very basics of creating your very first Web App. Don’t worry it will be a little bit more useful and interactive than a “Hello World” app. We will get the user to enter a value client-side. Then send it server-side for calculation and then return it back for display client-side.
Let’s take a look at what we are going to build:
Go on! Give it a try and see what happens.
Table of Contents
The example
In the example above the user enters a number. Up “Submit” the number is sent to Google Apps Script. It is first checked for whether a number has been added or not. If a number has been added, it will return the calculated number back to the client-side HTML and display it below the submit line.
If there is no number, then it will return an error text.
If there is an issue Google Apps Script-side, it will alternatively record an error.
We’ll also log the inputted data in Google Apps Script, just so you know it is all working.
Yeah, yeah. I know. We can do all this stuff client-side. This is just the basics so we can chat about how to carry out all the steps without overcomplicating it with something more show-offy (Don’t worry, we have some more fun in the next tutorial on Web Apps and take things up a notch).
The Code
This code will run two Google Apps Script files (.gs) and one HTML file (.html). Let’s take a look:
Code.gs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/**##################################### * Sets up server-side HTML environment */ function doGet() { return HtmlService.createHtmlOutputFromFile('Index') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); }; /**##################################### * Gets a value from server-side input. * Sends it to updateNumber to be updated. * Result will be returned server-side. * * @param {string} element - number as text from server-side * @return {string} contains evaluated number and test. */ function getNumberFromWebAPP(element){ //Just a quick proof that the returned number value will be a text. Remove on production. console.log(`Returned number is: ${element}`,typeof element); let num = parseInt(element); //Change text of num to number. //If the parsed number is a number, return calculated number otherwise return error. if(Number.isInteger(num)){ return updateNumber_(num); }; return `<em style='color:red'> You didn't input a number!`; }; /**##################################### * This function is an example of a location you can update your value in. * * @param {num} num - number from getNumberFromWebAPP() * @return {num} contains evaluated number. */ function updateNumber_(num){ return num + num; }; |
Test.gs
1 2 3 4 5 6 |
//run two versions num as text and empty string. function test(){ console.log(getNumberFromWebAPP("3"), getNumberFromWebAPP("")); }; |
Index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<!DOCTYPE html> <html> <head> <base target="_top" /> </head> <body> <h2>Tiny little Google Apps Script Web App</h2> <label for="numberInput"> Enter a number and click submit. </label> <input type="number" id="numberInput" name="numberInput" min="0" max="100" /> <span> </span> <input id="submitBtn" type="submit" value="Submit" onclick="sendData()" /> <div id="result"> <em> Your result will appear here!</em> </div> <script> /** * Send number to Google Apps Script and returns a calculated * value back to display on page. */ function sendData() { //Get inputted number and result div let number = document.querySelector('#numberInput').value; let updateLocation = document.querySelector('#result') //If the Google Apps script fails this error will be displayed // under result. // NOTE!!! not advisable for public deployment. function onFailure(error){ let warning = `<span style="color:red">${error}</span>`; updateLocation.innerHTML = warning; }; // The calculated result will be displayed in the result div. function onSuccess(element){ let result = ` Your result calculated from GAS is: ${element}`; updateLocation.innerHTML = result; }; /** First tests if GAS code is successful, * attempts to run GAS-side function. * on failure sends to onFailure function with nature of error. * on success sends to onSuccess function with returned result. */ google.script.run.withFailureHandler(onFailure) .withSuccessHandler(onSuccess) .getNumberFromWebAPP(number); }; </script> </body> </html> |
Code Breakdown
Code.gs
Taking a quick glance at the Code.gs file, you would probably expect a lot more going on here, but apart from the doGet()
function at the start, there should not be anything new for you if you have been playing with scripts for a little while.
doGet()
1 2 3 4 5 6 7 |
/**##################################### * Sets up server-side HTML environment */ function doGet() { return HtmlService.createHtmlOutputFromFile('Index') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); }; |
The doGet()
function’s role is to connect a server-side HTML document with the back-end Google Apps Script code (I am sure the die-hards will be having a meltdown over this oversimplification 🤣 ~🐐).
According to the Google Apps Script docs, for a Web App to work it requires a doGet()
or doPost()
function and that function returns a HTMLoutput
or TextOutput
of some flavour.
Our first task is to invoke the HTMLService which allows us to run client-side web pages. Line 5
There are a number of ways for us to generate and display HTML files in Google Apps Script. The most common is the createHtmlOutputFromFile()
method that creates and serves HTML from a *.HTML file that you can generate from the script editor.
This approach takes one argument, the title of the HTML file that you will be using. In our case, this is Index.html
and would be displayed like this:
...createHtmlOutputFromFile('Index')
Note that the file name does not have the file type extension.
If you were to run everything as it is, it would run just fine unembedded or if you were to embed it in a Google Site, but if you were to embed this into, say a WordPress document, like I have done in the example at the start, then you need to create one more line.
Google Apps Script HTML service provides its own controls against clickjacking by default. This means that if you left the HTML service with the defaut XFrame options, it will not show if you embed it in an external site. You will need to remove this and leave this in the hands of the site you wish to embed your Web App into. To do this we must call the setXFrameOptionsMode and change from its default settings by calling the HTML service again and setting the XFrameOptionMode
to ALLOWALL.
Great! You are all set. If you have some data in your Index.html file and deployed your web app you would see your HTML file displayed in your browser.
getNumberFromWebAPP(element)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/**##################################### * Gets a value from server-side input. * Sends it to updateNumber to be updated. * Result will be returned server-side. * * @param {string} element - number as text from server-side * @return {string} contains evaluated number and test. */ function getNumberFromWebAPP(element){ //Just a quick proof that the returned number value will be a text. Remove on production. console.log(`Returned number is: ${element}`,typeof element); let num = parseInt(element); //Change text of num to number. //If the parsed number is a number, return calculated number otherwise return error. if(Number.isInteger(num)){ return updateNumber_(num); }; return `<em style='color:red'> You didn't input a number!`; }; |
Our little web app will attempt to send a number to our server-side Code.gs
. On the client-side (Index.html
), we run a special Google code that will call a function in our Code.gs
file.
This function is getNumberFromWebAPP(element)
. During this process, a data element will be transferred over to our Google Apps Script. There are a number of things you can do with this element, but for us, we just want its data.
The data we should expect to see is number as a string. On line 12, we run a simple proof of this by logging the value of the element parameter and then checking its type with the Javascript typeof operator. We will use this later in our development to test to ensure what is coming in is what we expect. Then in production, we will delete this console logging line. Line 12
The logged result should look like this if the element
is 3:
Returned number is: 3 string
Our first real task is to convert our text number from a string to an integer and assign it to our num
variable. We do this with the parseInt() function that takes our element parameter. If it can convert it to a number, then it will display a number if not it will display NaN (Not a number). Line 14
Once this is done our next step is to validate num
with an ‘if’ statement. This time around I will use the Number.isInterger method on num
and if it returns true, then I know I have a number an can use it to run my calculation with the updateNumber_(num)
function and then return to complete getNumberFromWebAPP(element)
function. Lines 25-26
The calculated value will be then returned server-side for display to our users.
However, if your Number.isInteger
returns false, then we know this is not a number and we will return an error warning back to our HTML file. Line 28
Essentially, you want your function that is receiving the data to run validation on the data and set the data up to send to other working functions.
updateNumber_()
1 2 3 4 5 6 7 8 9 |
/**##################################### * This function is an example of a location you can update your value in. * * @param {num} num - number from getNumberFromWebAPP() * @return {num} contains evaluated number. */ function updateNumber_(num){ return num + num; }; |
This function is a quick example of something you might use to modify your data. You could just as easily create a function to send your data to a Google Doc or Sheet or transform it and return it in some other way. In the next tutorial, we will be doing something a little more interesting here but for now, this little function is a short proof and a suggested design of how you might like to layout your processes in your Google Apps Script file.
This function takes our num
parameter and doubles it before returning it to our main getNumberFromWebAPP(element)
to then be sent back server-side.
Test.gs
1 2 3 4 5 6 |
//run two versions num as text and empty string. function test(){ console.log(getNumberFromWebAPP("3"), getNumberFromWebAPP("")); }; |
Yeah, this is just a reminder to make sure you run some tests on your code.
To be honest, testing server-side HTML can be tricky with Google Apps Script at the moment. So these types of little tests can be really helpful.
In our test()
function, we are checking to see if our validation is going to work in the getNumberFromWebAPP()
function. We run the function with a test argument of a number as a string and then an empty string – the two conditions we are likely to receive. If our validation is working correctly we should get back the doubled value 6 as an integer and an error warning.
Nov 9, 2020, 9:34:52 PM Debug 6 '<em style=\'color:red\'> You didn\'t input a number!'
Index.html
Just like in our Code.gs file we have kept our HTML simple. It contains a basic HTML body without CSS for styling and then a script block.
Our Index.html file looks like this:
HTML
Here is just the HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<body> <h2>Tiny little Google Apps Script Web App</h2> <label for="numberInput"> Enter a number and click submit. </label> <input type="number" id="numberInput" name="numberInput" min="0" max="100" /> <span> </span> <input id="submitBtn" type="submit" value="Submit" onclick="sendData()" /> <div id="result"> <em> Your result will appear here!</em> </div> ... ... </body> |
We are using a simple number input with an id of 'numberInput'
for the user to select the number. This will force the user to either leave the input as blank or add a number. Just remember, a number input will still return a number as a string of text.
When the user clicks the submit button, the Javascript sendData()
function will run.
The calculated number will be returned in the div with the id, ‘result’.
Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
... <script> /** * Send number to Google Apps Script and returns a calculated * value back to display on page. */ function sendData() { //Get inputted number and result div let number = document.querySelector('#numberInput').value; let updateLocation = document.querySelector('#result') //If the Google Apps script fails this error will be displayed // under result. // NOTE!!! not advisable for public deployment. function onFailure(error){ let warning = `<span style="color:red">${error}</span>`; updateLocation.innerHTML = warning; }; // The calculated result will be displayed in the result div. function onSuccess(element){ console.log(element); let result = ` Your result calculated from GAS is: ${element}`; updateLocation.innerHTML = result; }; /** First tests if GAS code is successful, * attempts to run GAS-side function. * on failure sends to onFailure function with nature of the error. * on success sends to onSuccess function with returned result. */ google.script.run.withFailureHandler(onFailure) .withSuccessHandler(onSuccess) .getNumberFromWebAPP(number); }; </script> ... |
Setting up the function
The sendData()
function takes up the entirety of the script tag. This function will get the number and attempt to send it to the server-side Code.gs file calling the getNumberFromWebAPP(element)
function.
The functions first…erh...function is to get the user’s value from the number input. Line 9
It is probably also a good idea to grab the div that we are going to return the calculated number into as well. Line 10
Connecting with server-side…safely
Next, we have two functions. We’ll talk about these in a moment, but first, let’s cover our script line that sends our number to the Code.gs file on lines 32-34. Let’s break down each part so we know what is going on:
-
google.script.run
: This is an asynchronous client-side Google API that can call server-side functions in Google Apps Scripts. link.withFailureHandler(function)
: It is always a good idea to check if we can connect server-side or if there are any errors in the server-side code. We can do this with the failure handler. It will test the server connection and whether or not the server-side process works correctly. If there is a failure, it will return an error that we can log or display. This function also takes 1 argument, in our case, it is theonFailure()
function. Which will, you guessed it, run when there is a failure. More on this later. link.withSuccessHandler(function)
: If all goes well, we then run the success handler, which will run the call to the server-side function – in our case,getNumberFromWebAPP(number)
– and spit back our returned result and then initialises theonSuccess()
function we created above. link.getNumberFromWebAPP(number)
: This is the server-side function that initially receives the values in ourCode.gs
file. Both the success handler and failure handler will run this function. This will be where you add your own Google Apps Script function to run.
onFailure(error)
The onFailure
function is run if the google.script.run
failure handler encounters a problem. It takes one parameter, the error data generated from the failure handler.
1 2 3 4 5 6 7 8 9 |
... //If the Google Apps script fails this error will be displayed // under result. // NOTE!!! not advisable for public deployment. function onFailure(error){ let warning = `<span style="color:red">${error}</span>`; updateLocation.innerHTML = warning; }; ... |
On line 6, we create a warning variable, changing the colour to red to indicate a warning and then insert our error message. Then on the following line, we update the result div with our warning span message.
The failure handler error messages can be quite helpful and explicit. While this is very useful for you while developing your code, it might not be a great idea to share with your users. So you might want to consider more simple error message for them.
An on failure function can be much more than reporting an error message. You might use this function to receive the type of error message to try and affect a recall of the google.script.run
function after a delay or transport your user to another page.
onSuccess(element)
If our call to server-side was a success, we then run our onSuccess
function. This function takes a parameter generated from your serverside process or none at all.
1 2 3 4 5 |
// The calculated result will be displayed in the result div. function onSuccess(element){ let result = ` Your result calculated from GAS is: ${element}`; updateLocation.innerHTML = result; }; |
For our example, we are just creating a string containing a brief explanation of where the returned result was generated from and the returned calculated value held in the element
parameter.
Then we ship this results
text to our ‘results’ div to be displayed for the user.
Done!
That’s your code, but currently editing and testing your client-side index.html script can be a little tricky in the Google Apps Script editor.
Working With Front End Index.html
It can be a little tricky to troubleshoot and check for errors in the HTML file in your Google Apps Script editor right now. Even if you deploy your web app (more on this next), and check the Chrome browser developer tools you are not going to get very helpful error warnings.
My preferred approach is to do all the front end work in something like Visual Studio Code and put place-marker like console logs in where it is intended to connect with the Google API.
If you do have a heavy front end web app project, another approach might be to use CLASP to connect your Google Apps Script project with Visual Studio Code. You can check out my tutorial on this here:
Working with Google Apps Script in Visual Studio Code using clasp
Deploying Your Web App
Running, or more appropriately, deploying, your web app is a little different to running your everyday Apps Script.
To deploy your code for the first time from your Google Apps Script editor got to the menu bar and select Public >> Deploy as web app…
A popup window will appear:
First, enter the project version details. You could put something down like, “first edition” or “first test”. Make sure the selection is set to new.
Next, ensure that you execute the app as yourself for this project. Essentially this is saying that you want the app to have the same functionality that you are experiencing. Users will not be able to access your Google Workplace documents, Google Drive or even your Google Apps Script code unless you explicitly code this into your web app. So no need to worry.
Under the header Who has access to the app, it is advisable that on your first deployment that you deploy as yourself. That way you can test the app out, and if it is working how you want, then you can come back to this and deploy it later for anyone or for just your domain if you have a Google Workspace account.
Once you are done, hit the Deploy button. Another window will pop up baring the link to your client-side webpage.
Go on, copy the URL and paste it into your browser to see what you created.
If you have been playing along, your webpage should look like this:
Now if you are anything like me the first time I wrote a web app, you’d be a little annoyed about the warning in grey at the top of your shiny new app. Don’t worry, this will disappear once you embed it into a page.
Testing your Web App
So you are working on your project and you do a test deploy for the first time. You discover a bunch of errors. You do a few updates and save your code and refresh the deployed web app page and nothing happens.
What to do?
Head back to the editor menu and select Public >> Deploy as web app… again. Wait for the popup window to appear. This time around, you will notice a little difference.
Just under your current web app URL, you will notice this sentence:
Test web app for your latest code.
Click the link in blue and a test page will open that will display your latest update.
Keep this open while you edit your code and every time you make a save, refresh this test URL and you will see the modifications.
Final deploy of your web app
Once you are happy with your code, it’s time to redeploy it. Head back to Public >> Deploy as web app…
This time update your project version to New. Then briefly explain what changed in this update.
Next, if you are intending on sharing our project to a wider audience, update who has access to your app to Anyone (This will only display to those with a Gmail account or Google Workplace domain account), Anyone and anonymous (For everyone), or if you have a Google Workplace account, you should be able to select just the users in your domain.
Finally, smash that Update button. Another popup with your URL will appear. Select it and paste it into your browser for a quick test.
When you are ready, it’s time to embed it into your site.
Embedding your Web App
For us, we are going to embed our new web app into our WordPress blog above, but for most embeds in a website, these instructions will hold true. Check your content management service for more info if you get stuck.
Navigate to where you can insert raw HTML in our site. You will be inserting your code as an iframe.
Take a look at my code for the embedded web app I posted at the top of this post:
1 2 3 4 5 6 7 8 |
<iframe width="80%" height="200px" title="Tiny little Google Apps Script Web App" src="https://script.google.com/macros/s/AKfycbyuA0MxsJJN4XhAtV9v0VnasQ9IACuKUAzUEMaS19wlLwtsUaI/exec" frameborder="0" scrolling="no"> </iframe> |
I’ve set the width of the web app to 80% of the total width of the blog post space. I know all the data will fit into a height of 200 pixels so that is my height. Then, I give it a title so web crawlers for search engines know what is going on.
Next, goes the URL to my web app. You will need to put your own URL in here.
I want my web app to seem like it is part of the page, so I removed the default frame border by setting its thickness to zero and removed the scrolling on the iframe.
All done. Save or update your webpage and it should be displayed. Great job! You have a Google App Script web app on your page.
Final Words
Well, this was quite a journey. I had intended on quite a more complex example but once I built the web app realised that it was too distracting from understanding the core basics of building a web app. So instead, we will level up to this in the next tutorial and have some fun with calling Google Sheets data into our web app just like a mini database.
If you want to get a notification for when the next tutorial is out, why not subscribe? Top right-sidebar or the very bottom of this page. If you enjoyed the article, please give it a like and if you have some coffee money to keep me going writing these epic tutorials you can donate at the top right of my page.
Next Tutorial:
Creating an embedded interactive Chain Story app with Google Apps Script and Google Sheets
I would really love to hear what you have used this tutorial to help you make. It really inspires me and your fellow readers.
Need help with Google Workspace development?
Go something to solve bigger than Chat GPT?
I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
Schedule a free consultation today to discuss your needs and get started or learn more about our services here.
The min / max to not appear to affect what number can be input?
Once deployed I see there is a device in the input box which will populate in that range but any number can be entered. Sorry. I see no way to delete my hasty remark.
No worries. Glad you got it all working.
Thanks a ton for this very detailed tutorial! It helped me out a lot, as it was my first time making a web app. I made a website for my phone to copy/paste Gmail canned responses/templates. 🙂
That is awesome Crystal. I love the usecase. Thank you for sharing.
Hi Yagi!
Awesome example – your stuff is the best as always in explaining these concepts!! So thank you! However your link to the next example for Chain Story actually goes to “Creating Unique Ranges from 2D Arrays in Google Apps Script”
Your site is definitely the to-go place to learn how to code this stuff so thank you!!!
You’re welcome and thanks for the heads up. The link has been updated. I recently updated my site layout and while most of the URL redirects are working well some, like this one seem to have been missed.
Thanks,
Yagi