HTML Service: Templated HTML

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. */ ?>