HTML Service: Templated HTML

You can mix Apps Script code and HTML to produce dynamic pages with minimaleffort. If you've used a templating language that mixes code and HTML, such asPHP, ASP, or JSP, the syntax should feel familiar.

Scriptlets

Apps Script templates can contain three special tags, called scriptlets. Insidea scriptlet, you can write any code that would work in a normal Apps Scriptfile: scriptlets can call functions defined in other code files, referenceglobal variables, or use any of the Apps Script APIs. You can even definefunctions and variables within scriptlets, with the caveat that they can't becalled by functions defined in code files or other templates.

If you paste the example below into the script editor, the contents of the<?= ... ?> tag (aprinting scriptlet) will appear initalics. That italicized code runs on the serverbefore the page is servedto the user. Because scriptlet code executes before the page is served, itcan only run once per page; unlike client-side JavaScript or Apps Scriptfunctions that you call throughgoogle.script.run, scriptlets can'texecute again after the page loads.

Code.gs

function doGet() {  return HtmlService      .createTemplateFromFile('Index')      .evaluate();}

Index.html

<!DOCTYPE html><html>  <head>    <base>  </head>  <body>    Hello, World! The time is <?= new Date() ?>.  </body></html>

Note that thedoGet() function for templated HTML differs from the examplesforcreating and serving basic HTML. The functionshown here generates anHtmlTemplate object from the HTMLfile, then calls itsevaluate() method toexecute the scriptlets and convert the template into anHtmlOutput object that the scriptcan serve to the user.

Standard scriptlets

Standard scriptlets, which use the syntax<? ... ?>, execute code withoutexplicitly outputting content to the page. However, as this example shows, theresult of the code inside a scriptlet can still affect the HTML contentoutside of the scriptlet:

Code.gs

function doGet() {  return HtmlService      .createTemplateFromFile('Index')      .evaluate();}

Index.html

<!DOCTYPE html><html>  <head>    <base>  </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 oftheir code into the page using contextual escaping.

Contextual escaping means that Apps Script keeps track of the output’s contexton the page — inside an HTML attribute, inside a client-sidescript tag, oranywhere else — and automatically adds escape charactersto protect against cross-site scripting (XSS) attacks.

In this example, the first printing scriptlet outputs a string directly; it isfollowed by a standard scriptlet that sets up an array and a loop, followed byanother 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>  </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 standardscriptlet. So, for example, the scriptlet<?= 'Hello, world!'; 'abc' ?> onlyprints "Hello, world!"

Force-printing scriptlets

Force-printing scriptlets, which use the syntax<?!= ... ?>, are like printingscriptlets except that they avoid contextual escaping.

Contextual escaping is important if your script allows untrusted user input. Bycontrast, you’ll need to force-print if your scriptlet’s output intentionallycontains HTML or scripts that you want to insert exactly as specified.

As a general rule, use printing scriptlets rather than force-printing scriptletsunless 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 anyof the following three techniques to give your templates access to Apps Scriptdata.

Remember, however, that because template code executes before the page is servedto the user, these techniques can only feed initial content to a page. To access Apps Script data from a page interactively, use thegoogle.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, thenconstruct 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>  </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 exampleaccomplishes the same result as the previous example by loading the data in thetemplate itself rather than through a separate function.

Code.gs

function doGet() {  return HtmlService      .createTemplateFromFile('Index')      .evaluate();}

Index.html

<!DOCTYPE html><html>  <head>    <base>  </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 propertiesof theHtmlTemplate object. Onceagain, 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>  </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 executeddirectly; instead, the server transforms your template into code, then executesthat resulting code.

If it isn’t obvious how the template is interpreting your scriptlets, twodebugging methods in theHtmlTemplate class can help youbetter understand what's going on.

getCode()

getCode() returns astring containing the code that the server creates from the template. If youlog thecode, then paste it into the script editor, you can run it anddebug it like normalApps Script code.

Here's the simple template that displays a list of Google products again,followed by the result ofgetCode():

Code.gs

function myFunction() {  Logger.log(HtmlService      .createTemplateFromFile('Index')      .getCode());}

Index.html

<!DOCTYPE html><html>  <head>    <base>  </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 togetCode(), but returns the evaluated code as comments thatappear 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 implicitoutput object created by the methodHtmlService.initTemplate(). This methodis undocumented because only templates themselves need to use it.output is aspecialHtmlOutput object with twounusually named properties,_ and_$, which are shorthand for callingappend() andappendUntrusted().

output has one more special property,$out, which refers to a regularHtmlOutput object that does not possess these special properties. The templatereturns that normal object at the end of the code.

Now that you understand this syntax, the rest of the code should be fairly easyto follow. HTML content outside of scriptlets (like theb tag) is appendedusingoutput._ = (withoutcontextual 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 youget an error while running evaluated code, the line will correspond to theequivalent content in the template.

Hierarchy of comments

Because evaluated code preserves line numbers, it is possible for commentsinside scriptlets to comment out other scriptlets and even HTML code. Theseexamples 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 HTMLor in a <script> */ </script> tag,<? until you end the comment inside a scriptlet. */ ?>

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-06-04 UTC.