Tagged Template Literals in JavaScript

Hero image for Tagged Template Literals in JavaScript. Image by Bruno Nascimento.
Hero image for 'Tagged Template Literals in JavaScript.' Image by Bruno Nascimento.

Most developers meet template literals through the obvious features first:

  • string interpolation
  • multiline strings

That alone makes them useful. Yet ES6 also added a more powerful idea that tends to stay slightly mysterious for much longer: tagged template literals.

The name sounds more exotic than the feature really is.

A tagged template literal is just a template literal that is passed through a function before JavaScript turns it into a normal string. That function gets direct access to the static string parts and the interpolated values separately.

Once you see that clearly, the feature stops feeling magical and starts feeling practical.


A Normal Template Literal Evaluates to a String

This is the version most people already know:

const name = 'Ellie';const message = `Hello, ${name}!`;console.log(message);

That becomes the string Hello, Ellie!.

The interpolation happens automatically and the final result is just a string value.


A Tagged Template Literal Calls a Function Instead

If we put a function name before the template literal, JavaScript calls that function:

const logTemplate = (  strings: TemplateStringsArray,  ...values: unknown[]): string => {  console.log(strings);  console.log(values);  return 'done';};const result = logTemplate`Hello, ${'Ellie'}!`;

Now logTemplate receives:

  • an array of the literal string chunks
  • the interpolated values as separate arguments

For the example above, the rough shape is:

  • strings: ['Hello, ', '!']
  • values: ['Ellie']

That separation is the whole point.


Why This is Interesting

With an ordinary template literal, interpolation happens immediately and produces a plain string.

With a tagged template literal, we get a chance to inspect or transform the values first.

That means we can:

  • escape unsafe characters
  • format values consistently
  • build domainspecific minilanguages
  • preserve raw string fragments
  • reject or alter certain interpolations

This is why tagged templates show up in libraries such as CSS-in-JS tools and GraphQL helpers. The syntax reads naturally, but the underlying function still has full control.


A Simple Formatting Example

Suppose we want a small helper that wraps interpolated values in brackets for debugging:

const debug = (  strings: TemplateStringsArray,  ...values: unknown[]): string => {  return strings.reduce((result, stringPart, index) => {    const value = index < values.length ? `[${String(values[index])}]` : '';    return result + stringPart + value;  }, '');};const user = 'Ellie';const count = 3;console.log(debug`User ${user} has ${count} unread messages.`);

That produces:

User [Ellie] has [3] unread messages.

The important part is not the brackets. It is that the tag function decides how interpolated values are handled.


Escaping is a More Realistic Use Case

One practical example is HTML escaping.

If we are generating markup as a string, raw interpolation can be dangerous:

const unsafeName = '<script>alert("x")</script>';const html = `<p>${unsafeName}</p>`;

That string now contains the raw angle brackets.

With a tag function, we can escape the interpolated values before combining everything:

const escapeHtml = (value: string): string =>  value    .replaceAll('&', '&amp;')    .replaceAll('<', '&lt;')    .replaceAll('>', '&gt;')    .replaceAll('"', '&quot;')    .replaceAll("'", '&#39;');const safeHtml = (  strings: TemplateStringsArray,  ...values: unknown[]): string => {  return strings.reduce((result, stringPart, index) => {    const value =      index < values.length ? escapeHtml(String(values[index])) : '';    return result + stringPart + value;  }, '');};const unsafeName = '<script>alert("x")</script>';const output = safeHtml`<p>${unsafeName}</p>`;

Now the inserted value is escaped before it reaches the final string.


This is Useful, but It is Not Automatic Sanitisation

That caveat matters.

Tagged template literals do not make strings safe by themselves. The safety comes from what the tag function actually does.

If the tag function simply concatenates everything without escaping, nothing has been improved.

So the lesson is not "tagged templates are secure". The lesson is "tagged templates give you a clean place to enforce a rule".


The Raw Strings are Available Too

Template literals process escape sequences by default, but tag functions can also access the raw string fragments through strings.raw.

That becomes useful when the exact source text matters, for example with paths, regularexpressionlike patterns, or other custom parsing situations.

const inspectRaw = (  strings: TemplateStringsArray,  ...values: unknown[]): string => {  console.log(strings[0]);  console.log(strings.raw[0]);  return '';};inspectRaw`Line one\nLine two`;

The cooked string and raw string are not the same thing. That is part of what makes tagged templates flexible enough for library authors.


Most Application Code Does Not Need Them Everywhere

This is where restraint matters.

Tagged template literals are not a better replacement for every ordinary string. If all we need is:

const message = `Hello, ${name}`;

then a plain template literal is already the right tool.

The tag becomes worthwhile when we need policy as well as formatting:

  • consistent escaping
  • structured transformation
  • validation
  • custom parsing rules

Without that extra job to do, a tag function is just abstraction for its own sake.


Reading the Function Signature is the Key

Once people understand the function arguments, the feature becomes much less intimidating.

The tag function receives:

  1. the literal string pieces
  2. each interpolated value separately

After that, it can return whatever it likes. Often that is a string, but it does not have to be. A tag function can return an object, an array, or some other structured result if that suits the problem.

That is why the feature shows up in API design as well as string formatting.


Tagged Templates are Really About Control

Plain template literals are convenient because they make strings easier to write.

Tagged template literals go one step further. They make interpolation programmable.

That is the real mental model to keep hold of. The syntax looks like string creation, but the behaviour is functiondriven. Once you see it that way, the feature stops looking like obscure language trivia and starts looking like a neat way to centralise formatting and escaping rules.

You probably will not use template tags every day. Still, when you need to intercept interpolation cleanly, they are one of the nicest ideas ES6 introduced.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript