You can mix Apps Script code and HTML to produce dynamic pages with minimal effort. If you've used a templating language that mixes code and HTML, such as PHP, ASP, or JSP, the syntax should feel familiar.
Scriptlets
Apps Script templates can contain three special tags, called scriptlets. Inside a scriptlet, you can write any code that would work in a normal Apps Script file: scriptlets can call functions defined in other code files, reference global variables, or use any of the Apps Script APIs. You can even define functions and variables within scriptlets, with the caveat that they can't be called by functions defined in code files or other templates.
If you paste the example below into the script editor, the contents of the
<?= ... ?>
tag (a printing scriptlet) will appear in
italics. That italicized code runs on the server before the page is served
to the user. Because scriptlet code executes before the page is served, it
can only run once per page; unlike client-side JavaScript or Apps Script
functions that you call through
google.script.run
, scriptlets can't
execute again after the page loads.
Code.gs
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Hello, World! The time is <?= new Date() ?>.
</body>
</html>
Note that the doGet()
function for templated HTML differs from the examples
for creating and serving basic HTML. The function
shown here generates an
HtmlTemplate
object from the HTML
file, then calls its
evaluate()
method to
execute the scriptlets and convert the template into an
HtmlOutput
object that the script
can serve to the user.
Standard scriptlets
Standard scriptlets, which use the syntax <? ... ?>
, execute code without
explicitly outputting content to the page. However, as this example shows, the
result of the code inside a scriptlet can still affect the HTML content
outside of the scriptlet:
Code.gs
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? if (true) { ?>
<p>This will always be served!</p>
<? } else { ?>
<p>This will never be served.</p>
<? } ?>
</body>
</html>
Printing scriptlets
Printing scriptlets, which use the syntax <?= ... ?>
, output the results of
their code into the page using contextual escaping.
Contextual escaping means that Apps Script keeps track of the output’s context
on the page — inside an HTML attribute, inside a client-side script
tag, or
anywhere else — and automatically adds escape characters
to protect against cross-site scripting (XSS) attacks.
In this example, the first printing scriptlet outputs a string directly; it is followed by a standard scriptlet that sets up an array and a loop, followed by another printing scriptlet to output the contents of the array.
Code.gs
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<?= 'My favorite Google products:' ?>
<? var data = ['Gmail', 'Docs', 'Android'];
for (var i = 0; i < data.length; i++) { ?>
<b><?= data[i] ?></b>
<? } ?>
</body>
</html>
Note that a printing scriptlet only outputs the value of its first statement;
any remaining statements behave as if they were contained in a standard
scriptlet. So, for example, the scriptlet <?= 'Hello, world!'; 'abc' ?>
only
prints "Hello, world!"
Force-printing scriptlets
Force-printing scriptlets, which use the syntax <?!= ... ?>
, are like printing
scriptlets except that they avoid contextual escaping.
Contextual escaping is important if your script allows untrusted user input. By contrast, you’ll need to force-print if your scriptlet’s output intentionally contains HTML or scripts that you want to insert exactly as specified.
As a general rule, use printing scriptlets rather than force-printing scriptlets unless you know that you need to print HTML or JavaScript unchanged.
Apps Script code in scriptlets
Scriptlets aren’t restricted to running normal JavaScript; you can also use any of the following three techniques to give your templates access to Apps Script data.
Remember, however, that because template code executes before the page is served
to the user, these techniques can only feed initial content to a page. To access
Apps Script data from a page interactively, use the
google.script.run
API instead.
Calling Apps Script functions from a template
Scriptlets can call any function defined in an Apps Script code file or library. This example shows one way to pull data from a spreadsheet into a template, then construct an HTML table from the data.
Code.gs
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
function getData() {
return SpreadsheetApp
.openById('1234567890abcdefghijklmnopqrstuvwxyz')
.getActiveSheet()
.getDataRange()
.getValues();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? var data = getData(); ?>
<table>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
Calling Apps Script APIs directly
You can also use Apps Script code directly in scriptlets. This example accomplishes the same result as the previous example by loading the data in the template itself rather than through a separate function.
Code.gs
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? var data = SpreadsheetApp
.openById('1234567890abcdefghijklmnopqrstuvwxyz')
.getActiveSheet()
.getDataRange()
.getValues(); ?>
<table>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
Pushing variables to templates
Lastly, you can push variables into a template by assigning them as properties
of the HtmlTemplate
object. Once
again, this example accomplishes the same result as the previous examples.
Code.gs
function doGet() {
var t = HtmlService.createTemplateFromFile('Index');
t.data = SpreadsheetApp
.openById('1234567890abcdefghijklmnopqrstuvwxyz')
.getActiveSheet()
.getDataRange()
.getValues();
return t.evaluate();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<table>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
Debugging templates
Templates can be challenging to debug because the code you write is not executed directly; instead, the server transforms your template into code, then executes that resulting code.
If it isn’t obvious how the template is interpreting your scriptlets, two
debugging methods in the
HtmlTemplate
class can help you
better understand what's going on.
getCode()
getCode()
returns a
string containing the code that the server creates from the template. If you
log the
code, then paste it into the script editor, you can run it and
debug it like normal
Apps Script code.
Here's the simple template that displays a list of Google products again,
followed by the result of getCode()
:
Code.gs
function myFunction() {
Logger.log(HtmlService
.createTemplateFromFile('Index')
.getCode());
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<?= 'My favorite Google products:' ?>
<? var data = ['Gmail', 'Docs', 'Android'];
for (var i = 0; i < data.length; i++) { ?>
<b><?= data[i] ?></b>
<? } ?>
</body>
</html>
LOG (EVALUATED)
(function() { var output = HtmlService.initTemplate(); output._ = '<!DOCTYPE html>\n';
output._ = '<html>\n' +
' <head>\n' +
' <base target=\"_top\">\n' +
' </head>\n' +
' <body>\n' +
' '; output._$ = 'My favorite Google products:' ;
output._ = ' '; var data = ['Gmail', 'Docs', 'Android'];
for (var i = 0; i < data.length; i++) { ;
output._ = ' <b>'; output._$ = data[i] ; output._ = '</b>\n';
output._ = ' '; } ;
output._ = ' </body>\n';
output._ = '</html>';
/* End of user code */
return output.$out.append('');
})();
getCodeWithComments()
getCodeWithComments()
is similar to getCode()
, but returns the evaluated code as comments that
appear side-by-side with the original template.
Walking through evaluated code
The first thing you’ll notice in either sample of evaluated code is the implicit
output
object created by the method HtmlService.initTemplate()
. This method
is undocumented because only templates themselves need to use it. output
is a
special HtmlOutput
object with two
unusually named properties, _
and _$
, which are shorthand for calling
append()
and
appendUntrusted()
.
output
has one more special property, $out
, which refers to a regular
HtmlOutput
object that does not possess these special properties. The template
returns that normal object at the end of the code.
Now that you understand this syntax, the rest of the code should be fairly easy
to follow. HTML content outside of scriptlets (like the b
tag) is appended
using output._ =
(without contextual escaping),
and scriptlets are appended as JavaScript (with or without contextual escaping,
depending on the type of scriptlet).
Note that the evaluated code preserves line numbers from the template. If you get an error while running evaluated code, the line will correspond to the equivalent content in the template.
Hierarchy of comments
Because evaluated code preserves line numbers, it is possible for comments inside scriptlets to comment out other scriptlets and even HTML code. These examples show a few surprising effects of comments:
<? var x; // a comment ?> This sentence won't print because a comment begins inside a scriptlet on the same line. <? var y; // ?> <?= "This sentence won't print because a comment begins inside a scriptlet on the same line."; output.append("This sentence will print because it's on the next line, even though it's in the same scriptlet.”) ?> <? doSomething(); /* ?> This entire block is commented out, even if you add a */ in the HTML or in a <script> */ </script> tag, <? until you end the comment inside a scriptlet. */ ?>