Fill Html Template

Building approval emails and HtmlService notices by concatenating strings is how < in a client name becomes a broken page — or worse. fillHtmlTemplate(html, values, opts) replaces {{token}} placeholders from a values object, HTML-escapes by default, and supports a raw opt-in via {{{token}}} (triple braces) for trusted markup you already sanitized.

When to use

  • Approval / reject notices, digests, and simple HtmlService UIs
  • Any template where values come from a sheet (names, notes, amounts)
  • Swapping a brittle string-concat email for something readable

How to use this snippet

/** * Replace {{token}} (escaped) and {{{token}}} (raw) in an HTML string. * * @param {string} html * @param {Object<string, *>} values * @param {{onMissing?: 'blank'|'throw'}=} opts * onMissing 'blank' (default) leaves missing keys empty; * 'throw' fails loud when a placeholder has no value. * @return {string} */ function fillHtmlTemplate(html, values, opts) { opts = opts || {}; var onMissing = opts.onMissing || 'blank'; values = values || {}; function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } return String(html).replace( /\{\{\{([^{}]+)\}\}\}|\{\{([^{}]+)\}\}/g, function (_, rawKey, escKey) { var key = String(rawKey || escKey).trim(); var has = Object.prototype.hasOwnProperty.call(values, key); if (!has) { if (onMissing === 'throw') { throw new Error('fillHtmlTemplate: missing key "' + key + '"'); } return ''; } var val = values[key]; // Triple braces: caller opts into raw HTML (must already be safe) if (rawKey) return String(val == null ? '' : val); return escapeHtml(val); } ); }

Example

var NOTICE_TMPL = '<p>Hi {{requester}},</p>' + '<p>Your request <strong>{{id}}</strong> was {{status}}.</p>' + '<p>Note:</p>{{{noteHtml}}}'; function buildApprovalNotice_(row) { return fillHtmlTemplate(NOTICE_TMPL, { requester: row.Requester, id: row.Id, status: row.Status, // Pre-escape (or build from trusted parts) before using {{{…}}} noteHtml: '<em>' + escapeHtml(row.Note || '—') + '</em>' }, { onMissing: 'throw' }); }

Tips:

  • Default {{token}} is always escaped — sheet cells regularly contain &, <, and quotes.
  • Use {{{token}}} only for HTML you built yourself (or already ran through escapeHtml). Never dump a raw sheet cell into triple braces.
  • onMissing: 'blank' is fine for optional fields; use 'throw' for required ones so a typo in the template fails in the editor, not in someone’s inbox.
  • Whitespace inside the braces is trimmed ({{ name }} works). Nested braces aren’t supported — keep tokens flat.

Tip: NitroGAS Co-Pilot can turn a plain-English notice (“tell the requester their row was approved, include their note”) into a fillHtmlTemplate call with the right tokens.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension